[!NOTE]
能熟练掌握每种继承方式的手写实现,并知道该继承实现方式的优缺点。javascript
function Parent() { this.name = 'zhangsan'; this.children = ['A', 'B', 'C']; } Parent.prototype.getName = function() { console.log(this.name); } function Child() { } Child.prototype = new Parent(); var child = new Child(); console.log(child.getName());
[!NOTE]
主要问题:
1. 引用类型的属性被全部实例共享(this.children.push('name'))
2. 在建立Child的实例的时候,不能向Parent传参java
function Parent(age) { this.names = ['zhangsan', 'lisi']; this.age = age; this.getName = function() { return this.names; } this.getAge = function() { return this.age; } } function Child(age) { Parent.call(this, age); } var child = new Child(18); child.names.push('haha'); console.log(child.names); var child2 = new Child(20); child2.names.push('yaya'); console.log(child2.names);
[!NOTE]
优势:
1. 避免了引用类型的属性被全部实例共享
2. 能够直接在Child中向Parent传参
缺点:
方法都在构造函数中定义了,每次建立实例都会建立一遍方法数组
/** * 父类构造函数 * @param name * @constructor */ function Parent(name) { this.name = name; this.colors = ['red', 'green', 'blue']; } Parent.prototype.getName = function() { console.log(this.name); } // child function Child(name, age) { Parent.call(this, name); this.age = age; } Child.prototype = new Parent(); // 校订child的构造函数 Child.prototype.constructor = Child; // 建立实例 var child1 = new Child('zhangsan', 18); child1.colors.push('orange'); console.log(child1.name, child1.age, child1.colors); // zhangsan 18 (4) ["red", "green", "blue", "orange"] var child2 = new Child('lisi', 28); console.log(child2.name, child2.age, child2.colors); // lisi 28 (3) ["red", "green", "blue"]
[!NOTE]
优势: 融合了原型链继承和构造函数的优势,是Javascript中最经常使用的继承模式安全
------ 高级继承的实现app
function createObj(o) { function F(){}; // 关键:将传入的对象做为建立对象的原型 F.prototype = o; return new F(); } // test var person = { name: 'zhangsan', friends: ['lisi', 'wangwu'] } var person1 = createObj(person); var person2 = createObj(person); person1.name = 'wangdachui'; console.log(person1.name, person2.name); // wangdachui, zhangsan person1.friends.push('songxiaobao'); console.log(person2.friends); // lisi wangwu songxiaobao
[!WARNING]
缺点:
对于引用类型的属性值始终都会共享相应的值,和原型链继承同样函数
// 建立一个用于封装继承过程的函数,这个函数在内部以某种形式来加强对象 function createObj(o) { var clone = Object.create(o); clone.sayName = function() { console.log('say HelloWorld'); } return clone; }
[!WARNING]
缺点:与借用构造函数模式同样,每次建立对象都会建立一遍方法优化
function Parent(name) { this.name = name; this.colors = ['red', 'green', 'blue']; } Parent.prototype.getName = function() { console.log(this, name); } function Child(name, age) { Parent.call(this, name); this.age = age; } // test1: // 1. 设置子类实例的时候会调用父类的构造函数 Child.prototype = new Parent(); // 2. 建立子类实例的时候也会调用父类的构造函数 var child1 = new Child('zhangsan', 18); // Parent.call(this, name); // 思考:如何减小父类构造函数的调用次数呢? var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); // 思考:下面的这一句话能够吗? /* 分析:由于此时Child.prototype和Parent.prototype此时指向的是同一个对象, 所以部分数据至关于此时是共享的(引用)。 好比此时增长 Child.prototype.testProp = 1; 同时会影响 Parent.prototype 的属性的。 若是不模拟,直接上 es5 的话应该是下面这样吧 Child.prototype = Object.create(Parent.prototype);*/ Child.prototype = Parent.prototype; // 上面的三句话能够简化为下面的一句话 Child.prototype = Object.create(Parent.prototype); // test2: var child2 = new Child('lisi', 24);
// 自封装一个继承的方法 function object(o) { // 下面的三句话实际上就是相似于:var o = Object.create(o.prototype) function F(){}; F.prototype = o.prototype; return new F(); } function prototype(child, parent) { var prototype = object(parent.prototype); // 维护原型对象prototype里面的constructor属性 prototype.constructor = child; child.prototype = prototype; } // 调用的时候 prototype(Child, Parent)
var o1 = {name: 'value'}; var o2 = new Object({name: 'value'}); var M = function() {this.name = 'o3'}; var o3 = new M(); var P = {name: 'o4'}; var o4 = Object.create(P)
__proto__
内部属性,这个属性所对应的就是该对象的原型__proto__
以外,还预置了 prototype 属性__proto__
。任何一个实例对象经过原型链能够找到它对应的原型对象,原型对象上面!ui
的实例和方法都是实例所共享的。this
一个对象在查找以一个方法或属性时,他会先在本身的对象上去找,找不到时,他会沿着原型链依次向上查找。es5
注意: 函数才有prototype,实例对象只有有__proto__, 而函数有的__proto__是由于函数是Function的实例对象
判断实例对象的__proto__属性与构造函数的prototype是否是用一个引用。若是不是,他会沿着对象的__proto__向上查找的,直到顶端Object。
使用对象.construcor
直接可判断
var obj = {}; obj.__proto__ = Base.prototype; Base.call(obj);
类的声明
// 普通写法 function Animal() { this.name = 'name' } // ES6 class Animal2 { constructor () { this.name = 'name'; } }
在构造函数中 使用Parent.call(this)
的方法继承父类属性。
原理: 将子类的this使用父类的构造函数跑一遍
缺点: Parent原型链上的属性和方法并不会被子类继承
function Parent() { this.name = 'parent' } function Child() { Parent.call(this); this.type = 'child' }
原理:把子类的prototype(原型对象)直接设置为父类的实例
缺点:由于子类只进行一次原型更改,因此子类的全部实例保存的是同一个父类的值。
当子类对象上进行值修改时,若是是修改的原始类型的值,那么会在实例上新建这样一个值;
但若是是引用类型的话,他就会去修改子类上惟一一个父类实例里面的这个引用类型,这会影响全部子类实例
function Parent() { this.name = 'parent' this.arr = [1,2,3] } function Child() { this.type = 'child' } Child.prototype = new Parent(); var c1 = new Child(); var c2 = new Child(); c1.__proto__ === c2.__proto__
组合构造函数中使用call继承和原型链继承。
原理: 子类构造函数中使用Parent.call(this);
的方式能够继承写在父类构造函数中this上绑定的各属性和方法;
使用Child.prototype = new Parent()
的方式能够继承挂在在父类原型上的各属性和方法
缺点: 父类构造函数在子类构造函数中执行了一次,在子类绑定原型时又执行了一次
function Parent() { this.name = 'parent' this.arr = [1,2,3] } function Child() { Parent.call(this); this.type = 'child' } Child.prototype = new Parent();
由于这时父类构造函数的方法已经被执行过了,只须要关心原型链上的属性和方法了
Child.prototype = Parent.prototype;
缺点:
constructor
,此时直接使用父类的prototype的话那么会致使 实例的constructor为Parent,即不能区分这个实例对象是Child的实例仍是父类的实例对象。注意:这个时候instanseof是能够判断出实例为Child的实例的,由于instanceof的原理是沿着对象的__proto__判断是否有一个原型是等于该构造函数的原型的。这里把Child的原型直接设置为了父类的原型,那么: 实例.__proto__ === Child.prototype === Child.prototype
function Parent() { this.name = 'parent' this.arr = [1,2,3] } function Child() { Parent.call(this); this.type = 'child' } Child.prototype = Object.create(Parent.prototype); //提供__proto__ Child.prototype.constrctor = Child;
Object.create()方法建立一个新对象,使用现有的对象来提供新建立的对象的__proto__
/** * 工厂模式建立对象 * @param name * @return {Object} */ function createPerson(name){ var o = new Object(); o.name = name; o.getName = function() { console.log(this.name); } return o; } var person = createPerson('zhangsan'); console.log(person.__proto__ === Object.prototype); // true
缺点:没法识别当前的对象,由于建立的全部对象实例都指向的是同一个原型
/** * 使用构造函数的方式来建立对象 * @param name * @constructor */ function Person(name) { this.name = name; this.getName = function() { console.log(this.name) } } var person = new Person('lisi'); console.log(person.__proto__ === Person.prototype)
优势:实例剋识别伪一个特定的类型
缺点:每次建立实例对象的时候,每一个方法都会被建立一次
function Person(name) { this.name = name; this.getName = getName; } function getName() { console.log(this.name); } var person = new Person('zhangsan'); console.log(person.__proto__ === Person.prototype);
优势:解决了每一个方法都要被从新建立的问题
缺点:不合乎代码规范……
function Person(name) { } Person.prototype.name = 'lisi'; Person.prototype.getName = function() { console.log(this.name); } var person = new Person(); console.log(Person.prototype.constructor) // Person
优势:方法不会被从新建立
缺点:1. 全部的属性和方法全部的实例上面都是共享的;2. 不能初始化参数
function Person(name) { } Person.prototype = { name: 'lisi', getName: function() { console.log(this.name); } } var person = new Person(); console.log(Person.prototype.constructor) // Object console.log(person.constructor == person.__proto__.constructor) // true
优势:封装性好了一些
缺点:重写了Person的原型prototype属性,丢失了原始的prototype上的constructor属性
function Person(name) { } Person.prototype = { constructor: Person, name: 'lisi', getName: function() { console.log(this.name) } } var person = new Person();
优势:实例能够经过constructor属性找到所属的构造函数
缺点:全部的属性和方法都共享,并且不能初始化参数
function Person(name) { this.name = name; } Person.prototype = { constructor: Person, getName: function() { console.log(this.name) } } var person = new Person('zhangsan');
优势:基本符合预期,属性私有,方法共享,是目前使用最普遍的方式
缺点:方法和属性没有写在一块儿,封装性不是太好
// 第一种建立思路: function Person(name) { this.name = name; if (typeof this.getName !== 'function') { Person.prototype.getName = function() { console.log(this.name); } } } var person = new Person(); // 第二种建立的思路:使用对象字面量重写原型上的方法 function Person(name) { this.name = name; if (typeof this.getName !== 'function') { Person.prototype = { constructor: Person, getName: function() { console.log(this.name) } } return new Person(name); } } var person1 = new Person('zhangsan'); var person2 = new Person('lisi'); console.log(person1.getName()); console.log(person2.getName());
/** * 寄生构造函数模式 * @param name * @return {Object} * @constructor */ function Person(name){ var o = new Object(); o.name = name; o.getName = function() { console.log(this.name) } return o; } var person = new Person('zhangsan'); console.log(person instanceof Person); // false console.log(person instanceof Object); // true // 使用寄生-构造函数-模式来建立一个自定义的数组 /** * 特殊数组的构造器 * @constructor */ function SpecialArray() { var values = new Array(); /*for (var i = 0, len = arguments.length; i < len; i++) { values.push(arguments[i]); }*/ // 开始添加数据(能够直接使用apply的方式来优化代码) values.push.apply(values, arguments); // 新增的方法 values.toPipedString = function(){ return this.join('|'); } return values; } // 使用new来建立对象 var colors1 = new SpecialArray('red1', 'green1', 'blue1'); // 不使用new来建立对象 var colors2 = SpecialArray('red2', 'green2', 'blue2'); console.log(colors1, colors1.toPipedString()); console.log(colors2, colors2.toPipedString());
/** * 稳妥的建立对象的方式 * @param name * @return {number} * @constructor */ function Person(name){ var o = new Object(); o.sayName = function() { // 这里有点相似于在一个函数里面使用外部的变量 // 这里直接输出的是name console.log(name); } return o; } var person = Person('lisi'); person.sayName(); person.name = 'zhangsan'; person.sayName(); console.log(person instanceof Person); // false console.log(person instanceof Object); // false
[!NOTE] 与寄生的模式的不一样点:1. 新建立的实例方法不引用this 2.不使用new操做符调用构造函数 优势:最适合一些安全的环境中使用 缺点:和工厂模式同样,是没法识别对象的所属类型的