面向对象编程
目录
为什么要使用面向对象
对象是什么
生成对象
JavaScript_new命令
JavaScript_Prototype原型
构造函数的缺点
prototype 原型的作用
JavaScript_实例_静态属性和方法
实例方法和静态方法
实例属性和静态属性
JavaScript_proto__属性
JavaScript_constructor属性
JavaScript_原型链
JavaScript_instanceof运算符
JavaScript_Object对象的相关方法
Object.getPrototypeOf()
Object.setPrototypeOf()
Object.create()
Object.getOwnPropertyNames()
Object.prototype.hasOwnProperty()
JavaScript_对象的继承
JavaScript_多重继承
JavaScript_严格模式
设计目的
启用方法
显式报错
面向对象编程(Object Oriented Programming,缩写为 OOP)是目前主流的编程范式
为什么要使用面向对象
《大话设计模式》中大鸟给小菜讲的故事非常经典:
“话说三国时期,曹操带领百万大军攻打东吴,大军在长江赤壁驻扎,军船连成一片,眼看就要灭掉东吴,统一天下,曹操大悦,于是大宴众文武,在酒席间,曹操诗性大发,不觉吟道:‘喝酒唱歌,人生真爽……’众文武齐呼:‘丞相好诗!’于是一臣子速命印刷工匠刻版印刷,以便流传天下。”
“样张出来给曹操一看,曹操感觉不妥,说道:‘喝与唱,此话过俗,应改为‘对酒当歌’较好!’于是此臣就命工匠重新来过。工匠眼看连夜刻版之工,彻底白费,心中叫苦不迭。只得照办。”
“样张再次出来请曹操过目,曹操细细一品,觉得还是不好,说:‘人生真爽‘太过直接,应改问语才够意境,因此应改为‘对酒当歌,人生几何……’当臣子转告工匠之时,工匠晕倒……”
大鸟:“小菜你说,这里面问题出在哪里?”
小菜:“是不是因为三国时期活字印刷还未发明,所以要改字的时候,就必须要整个刻板全部重新刻。”
大鸟:“说得好!如果是有了活字印刷,则只需更改四个字就可,其余工作都未白做。岂不妙哉。
一、要改,只需更改要改之字,此为可维护;
二、这些字并非用完这次就无用,完全可以在后来的印刷中重复使用,此乃可复用;
三、此诗若要加字,只需另刻字加入即可,这是可扩展;
四、字的排列其实可能是竖排可能是横排,此时只需将活字移动就可做到满足排列需求,此是灵活性好。”
“而在活字印刷术出现之前,上面的四种特性都无法满足,要修改,必须重刻,要加字,必须重刻,要重新排列,必须重刻,印完这本书后,此版已无任何可再利用价值。”
小菜:“是的,小时候我一直奇怪,为何火药、指南针、造纸术都是从无到有,从未知到发现的伟大发明,而活字印刷仅仅是从刻版印刷到活字印刷的一次技术上的进步,为何不是评印刷术为四大发明之一呢?原来活字印刷是思想的成功,面向对象的胜利。”
对象是什么
要了解面向对象编程,要先了解对象是什么?
对象其实是一个抽象概念的具体实例,例如:
- 人类:张三
- 动物:猫
抽象概念:人类、动物 特别常见称呼:类、模板
对象:张三、猫 特别常见称呼:实例对象
生成对象
对象是一个抽象概念的具体实例,那么我们要生成一个对象,需要有一个模板,表示某一类实物的共同特征,然后对象根据这个模板生成。
JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。
构造函数就是一个普通的函数,但具有自己的特征和用法
function People(){ this.name = "张三"}
温馨提示
构造函数的首字母大写:例如
People
中的P
是大写的
构造函数的特点有两个
- 函数体内部使用了
this
关键字,代表了所要生成的对象实例 - 生成对象的时候,必须使用
new
命令
function People(){ this.name = "张三"}var p = new People();p.name // 张三
JavaScript_new命令
new
命令的作用,就是执行构造函数,返回一个实例对象
使用new
命令时,根据需要,构造函数也可以接受参数
this
关键字在构造函数中的
this
指向当前实例对象
Document /* 理解面向对象 1. 为什么要使用面向对象 1. 易维护 2. 可复用 3. 可扩展 4. 灵活性 2. 面向对象的代码编写 1. 理解面向对象的模板(构造函数) 2. 通过模板生成一个实例对象(new关键字) */ /* this关键字: 1. 事件中this指向DOM元素 2. 闭包中的this指向window 3. 定时器中的this指向window 4. 对象中的this指向调用者 5. 改变this指向:call\apply\bind 6. 构造函数中的this,指向当前实例对象 */ function Person(name,age){ this.name = name; this.age = age; } var zhangsan = new Person("张三",20); var lisi = new Person("李四",22); console.log(zhangsan); console.log(lisi);
JavaScript_Prototype原型
构造函数的缺点
JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部
function People(name,age) { // 属性 this.name = name; this.age = age; // 方法; this.sayHello = function(){ console.log("Hello"); }}var p1 = new People("张三",20);var p2 = new People("李四",30);
通过构造函数为实例对象定义属性和方法,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性和方法,从而造成对系统资源的浪费
console.log(p1.sayHello === p2.sayHello); // false
这个问题的解决方法,就是 JavaScript 的原型对象(prototype)
prototype 原型的作用
prototype
原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,节省了内存。
怎么为对象指定原型呢,JavaScript 规定,每个函数都有一个prototype
属性,指向一个对象
function People() { }console.log(People.prototype);
对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。
function People(name,age) { // 属性 this.name = name; this.age = age;}// 原型属性People.prototype.color = "黄种人";// 原型方法People.prototype.sayHello = function(){ console.log("Hello");}var p1 = new People("张三",20);var p2 = new People("李四",30);console.log(p1.sayHello === p2.sayHello); // trueconsole.log(p1.color === p2.color); // true
p1
和p2
共享了原型属性和方法
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
People.prototype.color = "白种人";p1.color // 白种人p2.color // 白种人
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法
p1.color = "白种人"console.log(p1.color); // "白种人"
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。
JavaScript_实例_静态属性和方法
实例方法和静态方法
在JavaScript中有静态方法和实例方法,静态方法是函数自己定义的,而实例方法是通过原型来定义。它们的区别是静态方法是可以直接用类名.方法名
去调用的,而实例方法是不可以的,它必须要用实例才可以去调用实例.方法名
。
function Person(name){ this.name = name;}// 实例方法Person.prototype.getName = function(){ console.log(this.name);}// 静态方法Person.getAge = function(age){ console.log(age);}var person = new Person("itbaizhan");person.getName();Person.getAge(10);
JavaScript内置的Array方法则分为实例方法和静态方法
var arr = [10,20,30];// 实例方法arr.push(40);// 静态方法Array.isArray(arr);
实例属性和静态属性
实例属性和静态属性与方法类似,实例属性是通过实例对象调用的实例对象.属性
,静态属性是通过类名调用的类名.属性
function Person(name){ // 实例属性 this.name = name;}// 静态属性 Person.age = 20;var person = new Person("itbaizhan");console.log(person.name);console.log(Person.age);
JavaScript_proto__属性
实例对象的__proto__
属性(前后各两个下划线),返回该对象的原型。该属性可读写
function People(){}var p = new People();console.log(p.__proto__);
对象的prototype
属性等同于实例对象的__proto__
属性
function People(){}var p = new People();console.log(p.__proto__ === People.prototype); // true
温馨提示
根据语言标准,
__proto__
属性只有浏览器才需要部署,其他环境可以没有这个属性
JavaScript_constructor属性
prototype
对象有一个constructor
属性,默认指向prototype
对象所在的构造函数
function Person(){}console.log(Person.prototype.constructor === Person); // true
constructor
属性的作用是什么呢
通俗的讲,就是为了将实例原型对象暴露出来, 比如你写了一个插件,别人得到的都是你实例化后的对象, 如果别人想扩展下对象,就可以用 constructor.prototype
去修改或扩展原型对象
// function Person(name){ // this.name = name // } // // 获得到原型 // console.log(Person.prototype.constructor); var person; (function(){ function Person(){ this.name = "张三"; this.age = 20; } Person.prototype.getName = function(){ console.log(this.name); } person = new Person(); })(); person.getName() // 问题:现在这个库缺少了一个方法,getAge的方法 // Person.prototype.getAge = function(){ // } person.constructor.prototype.getAge = function(){ console.log(this.age); } person.getAge();
JavaScript_原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型...
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype
,即Object
构造函数的prototype
属性。也就是说,所有对象都继承了Object.prototype
的属性。这就是所有对象都有valueOf
和toString
方法的原因,因为这是从Object.prototype
继承的。
function Person(){}console.log(Person.prototype.__proto__);console.log(Object.prototype);
那么,Object.prototype
对象有没有它的原型呢?回答是Object.prototype
的原型是null
。null
没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null
。
console.log(Object.prototype.__proto__); // null
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype
还是找不到,则返回undefined
。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)
function Person(){}Person.prototype.toString = function(){ return "这是Person的toString方法"}var person = new Person();person.toString()
JavaScript_instanceof运算符
instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例
function Person(){}var person = new Person();console.log(person instanceof Person);
由于instanceof
检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true
。
var d = new Date();d instanceof Date // trued instanceof Object // true
instanceof
运算符的一个用处,是判断值的类型
var arr = [10,20,30];var obj = {};console.log(arr instanceof Array);console.log(obj instanceof Object);
温馨提示
instanceof
运算符只能用于对象,不适用原始类型的值。
var str = 'itbaizhan';str instanceof String // false
function Person(){} var p = new Person(); var p1 = []; console.log(p instanceof Person); // true:当前p是Person的实例对象 console.log(p1 instanceof Person); // false:p1不是Person的实例对象 /* typeof:检测基本数据类型:number、string、boolean isArray:检测数组:true,false instanceof:检测对象:true,false */ // intanceof会检测整个原型链 console.log(p instanceof Object); // true console.log(p1 instanceof Object); // true // var num = 10;// instanceof:只能检测对象 // console.log(num instanceof Number); //false var arr = []; console.log(arr instanceof Array);
JavaScript_Object对象的相关方法
Object.getPrototypeOf()
Object.getPrototypeOf
方法返回参数对象的原型。这是获取原型对象的标准方法
function Person(){} Person.prototype.getName = function(){} var p = new Person() // 获取实例对象的原型对象 console.log(Object.getPrototypeOf(p)); console.log(Person.prototype); // 不推荐使用 console.log(p.__proto__);
Object.setPrototypeOf()
Object.setPrototypeOf
方法为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象
var sxt = {}; var itbaizhan = { teacher:"老师", getAge:function(){ console.log(13); } } Object.setPrototypeOf(sxt,itbaizhan); sxt.getAge() console.log(sxt.teacher);
Object.create()
JavaScript 提供了Object.create()
方法,让一个对象继承另一个对象的属性和方法
var sxt = { teacher: function () { console.log('hello'); }};var itbaizhan = Object.create(sxt);itbaizhan.teacher() // hello
Object.create()
方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上
var sxt = { t: 10 };var itbaizhan = Object.create(sxt);sxt.t = 20;itbaizhan.t // 20
除了对象的原型,Object.create()
方法还可以接受第二个参数。该参数是一个属性描述对象,它所描述的对象属性,会添加到实例对象,作为该对象自身的属性
var sxt = { java: { value: "全体系" }, web: { value: '大前端' }};var itbaizhan = Object.create(sxt, { python:{//必须是对象形式value:"全方向" }});console.log(itbaizhan.java.value);console.log(itbaizhan.python);//全方向 注意调用形式
Object.getOwnPropertyNames()
Object.getOwnPropertyNames
方法返回一个数组,成员是参数对象本身的所有属性的键名,不包含继承的属性键名
Object.getOwnPropertyNames(Array)// ['length', 'name', 'prototype', 'isArray', 'from', 'of']
Object.prototype.hasOwnProperty()
对象实例的hasOwnProperty
方法返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上
console.log(Object.getOwnPropertyNames(Array)); console.log(Array.hasOwnProperty("length")); // true console.log(Array.hasOwnProperty("toString")); // false
JavaScript_对象的继承
面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。
大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现
温馨提示
ES6版本已经提供了 class 语法
function Person(name,age){ this.name = name; this.age = age; } Person.prototype.getName = function(){ console.log(this.name); } Person.prototype.getAge = function(){ console.log(this.age); } function Student(name,age,learn){ // 继承属性:调用父类的构造函数 Person.call(this,name,age); this.learn = learn; } // 继承父类的方法 for(var p in Person.prototype){ Student.prototype[p] = Person.prototype[p]; } Student.prototype.getLearn = function(){ console.log(this.learn); } // 就近原则 Student.prototype.getName = function(){ console.log("姓名:"+this.name); } var student = new Student("张三",19,"it"); student.getAge() student.getName() student.getLearn()
JavaScript_多重继承
JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能
function Sxt(){ this.hello = "hello" } Sxt.prototype.getHello = function(){ console.log("Hello"); } function Itbaizhan(){ this.world = "world" } Itbaizhan.prototype.getWorld = function(){ console.log("World"); } function Sum(){ Sxt.call(this); Itbaizhan.call(this); } // 继承Sxt // Sum.prototype = Object.create(Sxt.prototype); for(var p in Sxt.prototype){ Sum.prototype[p] = Sxt.prototype[p]; } // 继承Itbaizhan Object.assign(Sum.prototype,Itbaizhan.prototype); // 指定constructor Sum.prototype.constructor = Sum; console.log(Sum.prototype); var s = new Sum(); console.log(s.hello); console.log(s.world); s.getHello(); s.getWorld();
JavaScript_严格模式
除了正常的运行模式,JavaScript 还有第二种运行模式:严格模式(strict mode)。顾名思义,这种模式采用更加严格的 JavaScript 语法。
同样的代码,在正常模式和严格模式中,可能会有不一样的运行结果。一些在正常模式下可以运行的语句,在严格模式下将不能运行。
设计目的
早期的 JavaScript 语言有很多设计不合理的地方,但是为了兼容以前的代码,又不能改变老的语法,只能不断添加新的语法,引导程序员使用新语法。
严格模式是从 ES5 进入标准的,主要目的有以下几个。
- 明确禁止一些不合理、不严谨的语法,减少 JavaScript 语言的一些怪异行为。
- 增加更多报错的场合,消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 为未来新版本的 JavaScript 语法做好铺垫。
总之,严格模式体现了 JavaScript 更合理、更安全、更严谨的发展方向。
启用方法
进入严格模式的标志,是一行字符串use strict
'use strict';
use strict
放在脚本文件的第一行,整个脚本都将以严格模式运行use strict
放在函数体的第一行,则整个函数以严格模式运行
'use strict'; console.log('这是严格模式');function strict() { 'use strict'; return '这是严格模式';}
显式报错
eval、arguments 不可用作标识名
严格模式下,使用eval
或者arguments
作为标识名,将会报错。下面的语句都会报错
'use strict';var eval = 17;var arguments = 17;
函数不能有重名的参数
正常模式下,如果函数有多个重名的参数,可以用arguments[i]
读取。严格模式下,这属于语法错误
function f(a, a, b) { 'use strict'; return a + b;}
全局变量显式声明
正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明
'use strict';v = 1; // 报错,v未声明for (i = 0; i < 2; i++) { // 报错,i 未声明 // ...}function f() { x = 123;}f() // 报错,未声明就创建一个全局变量
禁止 this 关键字指向全局对象
禁止使用 with 语句
arguments 不再追踪参数的变化
保留字
为了向将来 JavaScript 的新版本过渡,严格模式新增了一些保留字(implements、interface、let、package、private、protected、public、static、yield等)。使用这些词作为变量名将会报错
function package(protected) { // 语法错误 'use strict'; var implements; // 语法错误}
选项卡(面向对象实现)
Document .tab input { background: #f1f1f1; border: 1ps solid #ff0000; } .tab .active { background: #e9f212; } .tab div { display: none; width: 300px; height: 250px; padding: 10px; background: #e9f212; border: 1px solid #ff0000; } function Tab(id) { var tabBox = document.getElementById(id); // 属性 this.tabBtn = tabBox.getElementsByTagName("input"); this.tabDiv = tabBox.getElementsByTagName("div"); for (var i = 0; i < this.tabBtn.length; i++) { this.tabBtn[i].index = i; var _this = this; this.tabBtn[i].onclick = function () { _this.clickBtn(this); } } } Tab.prototype.clickBtn = function (btn) { for (var j = 0; j < this.tabBtn.length; j++) { this.tabBtn[j].className = ""; this.tabDiv[j].style.display = "none"; } this.className = "active"; this.tabDiv[btn.index].style.display = "block" } // window.onload = function () { // new Tab("tabBox1") // new Tab("tabBox2") // } // 每一个选项卡在加载之后,都知道这个选共享卡是干嘛,或者是输出专业 function ItbaizhanTab(id,effect){ Tab.call(this,id); this.effect = effect; } for(var t in Tab.prototype){ ItbaizhanTab.prototype[t] = Tab.prototype[t] } ItbaizhanTab.prototype.getEffect = function(){ console.log(this.effect); } window.onload = function(){ var t1 = new ItbaizhanTab("tabBox1","bz程序员主流专业"); var t2 = new ItbaizhanTab("tabBox2","bz程序员参考"); t1.getEffect(); t2.getEffect(); } React、Vue SpringBoot、SpringMVC flask sxt出品java sxt视频,bz视频 课堂笔记文档