最近在复习基础知识,特对js面向对象相关知识做个记录
js中的面向对象编程基础知识
创建对象
字面量模式
最简单也是最常用创建简单对象的方式
12345678var personCgh={name:'cgh',age:'18',sex:'boy',sayName:function(){alert(this.name);}};缺点
- 如果需要创建大量类似的对象,会产生大量重复代码
工厂模式
抽象(封装)创建具体对象的过程
123456789101112function createPersonFactory(name,age,sex){var o=new Object();o.name=name;o.age=age;o.sex=sex;o.sayName=function(){alert(this.name);};return o;}var personCgh=createPersonFactory('cgh',18,'boy');解决了字面量模式创建多个类似对象的问题
- 缺点
- 无法知道一个对象的类型
构造函数模式
通过创建自定义构造函数并通过new来创建实例
12345678910111213function Person(name,age,sex){this.name=name;this.age=age;this.sex=sex;this.sayName=function(){alert(this.name);};}var personCgh=new Person('cgh',18,'boy');personCgh instanceof Person; // truepersonCgh instanceof Object; // true缺点
- 每个方法都需要在实例上重新创建一遍
原型模式
- 每个函数在创建时都会自动生成一个原型(
prototype
)属性,指向函数的原型对象;原型对象会自动获得一个constructor
属性指向prototype
属性所在的函数; 实例对象、构造函数、原型对象三者关系
- 构造函数被创建时,会有一个
prototype
属性指向原型对象 - 原型对象通过
constructor
反向指回构造函数 - 构造函数通过实例化过程(new过程)创建实例对象
- 实例对象通过内部
[[prototype]]
或者__proto__
属性(不可直接访问)指向原型对象12345678910111213141516171819202122function Person(){}Person.prototype={constructor:Person,// 因为完全重写了prototype对象,所以必须定义constructor指向name:'cgh',age:18,sex:'boy',friends:['a','b'],sayName:function(){alert(this.name)}};var personCgh=new Person();var personYg=new Person();personCgh.friends.push('c');personCgh.friends; // ['a','b','c']personYg.friends; // ['a','b','c']personCgh.name === personYg.name; // true
- 构造函数被创建时,会有一个
缺点
- 会存在共享引用值的问题
组合使用构造函数模式和原型模式
- 使用构造函数创建实例属性,用原型模式创建共享的方法和属性
- 常用模式12345678910111213141516171819function Person(name,age,job){this.name=name;this.age=age;this.job=job;this.friends=['a','b'];}Person.prototype={constructor:Person,sayName:function(){alert(this.name);}};var personCgh=new Person('cgh',18,'boy');var personYg=new Person('yg',14,'boy');personCgh.friends.push('c'); // ['a','b','c']personYg.friends; // ['a','b']
new的过程
- new Fn操作符主要完成了下面几件事情
- 创建一个新的空对象
tempObj
,空对象的默认原型对象(__proto__
)为Object.prototype
- 设置空对象的
__proto__
指向构造函数的原型对象; - 调用构造函数,构造函数的
this
指向tempObj
; - 如果构造函数返回的是一个
非null
的引用类型的对象,则用此对象替代tempObj
成为new操作的返回对象 - new会将返回对象的
__proto__
指向构造函数的.prototype
对象12345678910111213function Test(){this.name='test';}var test=new Test();// new Test实际执行的伪代码...{var temp={};// 创建临时对象temp.__proto__=Test.prototype;// 改变__ptoto__指向var ret=Test.call(tempObj);return ret!==null && (typeof ret === 'object'|| typeof ret === 'function') ? ret : temp ;// 如果构造函数调用后返回的是非null的引用类型,则用其替代temp做为new操作符的返回值}
- 创建一个新的空对象
继承
使用原型链实现继承
让一个类(构造函数,基类)的原型对象指向另一个类(构造函数,父类)的实例即可
123456789101112131415161718192021function SuperType(){this.property=true;}SuperType.prototype.getSuperValue=function(){return this.property;};function SubType(){this.subProperty=false;}// 继承父类 基类的原型对象指向父类的一个实例SubType.prototype=new SuperType();SubType.prototype.getSubVaule=function(){return this.subProperty;};var instance=new SubType();instance.getSuperValue();// true如果基类要覆盖父类方法一定要在继承之后再覆盖
- 缺点
- 父类上定义的引用类型值将会被共享
- 无法在不影响所有对象实例的基础上,给父类的构造函数传递参数
借用构造函数实现继承
通过在子类的构造函数中调用父类的构造函数来实现继承
12345678910111213141516function SuperType(name){this.colors=['red','blue']this.name=name;}function SubType(){SuperType.call(this);}var instance1=new SubType('cgh');instance1.colors.push('green');var instance2=new SubType('yg');instance1.colors;// ['red','blue','green']instance2.colors;// ['red','blue']缺点
- 方法都将在构造函数中定义,无法实现方法的复用
组合继承
- 借用构造函数+原型链
- 使用原型链实现对原型属性、方法的继承,使用借用构造函数实现实例属性的继承1234567891011121314151617181920212223242526272829303132333435function SuperType(name){this.name=name;this.colors=['red','blue'];}SuperType.prototype.sayName=function(){alert(this.name);};function SubType(name,age){// 继承属性SuperType.call(this,name);this.age=age;}// 继承方法SubType.prototype=new SuperType();SubType.prototype.constructor=SubType;SubType.prototype.sayAge=function(){alert(this.age);};var instance1=new SubType('cgh',20);instance1.colors.push('green');instance1.colors;// ['red','blue','green'];instance1.sayName();// 'cgh'instance1.sayAge();// 20var instance2=new SubType('yg',18);instance1.colors.push('pink');instance1.colors;// ['red','blue','pink'];instance1.sayName();// 'yg'instance1.sayAge();// 18
原型继承
- 通过改变对象内部的
__proto__
指针来实现 - 不需要显示的创建构造函数
思路直接以一个对象为原型,从其克隆出一个新对象
123456789function object(o){function F(){}F.prototype=o;// 指定原型对象return new F();// 通过new 调用将返回对象的__proto__指向F.prototype即o}// 本质是将返回对象的__proto__指向了传入对象var returnObj=object(testObj);returnObj.__proto__===testObj;//truees5对这种方式做了标准化。直接使用
Object.create()
来实现12345678910var a={name:'cgh',sayName:function(){alert(this.name);}};var b=Object.create(a);b.name='yg';b.sayName();// yg
实现继承的本质
js
中实现继承的关键在__proto__
这个内部指针首先基础知识
- js中万物都是对象,没有实际的类(可以靠模拟实现类的效果)、函数、构造函数(本质就是普通函数)、原型对象(
xxx.prototype
),这些都是对象 - 每个对象都有
__proto__
这个内部指针 - 每个函数都有
.prototype
这个属性 - 所有函数都是对象;所以函数既有
__proto__
也有.prototype
- 内部通过
__proto__
来构建原型链实现类似继承效果,即方法属性的查找通过__proto__
链来一层层查找;(类似作用域链的回溯查找) - 所有原型链最后端都是
Object.prototype
,Object.prototype
这个对象的__proto__
则指向了null
__proto__
和fn.prototype
不一样,前者才是实现继承的关键,后者只是为了模拟传统类继承而衍生出来方便表达的属性。可以当作一个普通对象对待(但它又有不同,它默认自带constructor
指回函数)12345678910111213141516171819202122232425262728293031323334var o = {a: 1};// o 这个对象继承了Object.prototype上面的所有属性// o 自身没有名为 hasOwnProperty 的属性// hasOwnProperty 是 Object.prototype 的属性// 因此 o 继承了 Object.prototype 的 hasOwnProperty// Object.prototype 的原型为 null// 原型链如下:// o ---> Object.prototype ---> null// o.__proto___===Object.prototype;// true// Object.prototype.__proto__===null;// truevar a = ["yo", "whadup", "?"];// 数组都继承于 Array.prototype// (Array.prototype 中包含 indexOf, forEach等方法)// 原型链如下:// a ---> Array.prototype ---> Object.prototype ---> null// a.__proto__===Array.prototype;// true// Array.prototype.__proto__===Object.prototype;// true// Object.prototype.__proto__===null;// truefunction f(){return 2;}// 函数都继承于Function.prototype// (Function.prototype 中包含 call, bind等方法)// 原型链如下:// f ---> Function.prototype ---> Object.prototype ---> null// f.__proto__===Function.prototype;// true// Function.prototype.__proto__===Object.prototype;// true// Object.prototype.__proto__===null;// true
- js中万物都是对象,没有实际的类(可以靠模拟实现类的效果)、函数、构造函数(本质就是普通函数)、原型对象(
js中万物都是对象,不一定非要模拟传统语言先有类(模板)再创建实例的形式来实现继承效果呢?为什么不能直接用一个对象做为模板,直接克隆出另一个类似对象呢?js内部通过
__proto_
,来实现两个对象之间的关联(委托)关系,进而实现类似继承的效果。- 原型继承和
Object.create()
都是类似思想,直接通过建立两个对象间的委托关系,实现继承效果12345678910var a={name:'cgh',sayName:function(){alert(this.name);}};var b=Object.create(a);b.name='yg';b.sayName();// yg
- 原型继承和
__proto__
是一个内部属性,es6对其做了规范可以通过Object.getPrototypeOf
和对应的Object.setPrototypeOf
来操作__proto__
参考链接
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
https://www.zhihu.com/question/34183746