Open lznbuild opened 4 years ago
function Parent () { this.name = 'kevin'; } Parent.prototype.getName = function () { console.log(this.name); } function Child () { } //可以这样理解,Child.prototype成为Parent的实例,实例上有_proto_属性,自然可以访问Parent原型上的属性和方法 // Child.prototype.__proto__ === Parent.prototype // true Child.prototype = new Parent(); var child1 = new Child(); console.log(child1.getName()) // kevin,注意,getName()不在child1上,在原型对象上
function Parent () { this.names = ['kevin', 'daisy']; }
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
child1.names.push('yayu'); // 修改child1的names属性,child1自身没有这个属性,会去其原型对象上查找,修改了原型对象上的names引用的那个数组 // 注意,names不在child1上,而是在child1的原型对象上 console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy", "yayu"] // 这个也好理解,Child.prototype是 Parent的实例,Parent函数中的this自然指向Child.prototype,相当于在Child.prototype中声明names属性并赋值一个数组,Child的实例上并没有names属性,都是共享同一个原型对象上的names属性,而实例child1修改Child原型上的引用数据类型的变量,Chlid实例的names属性引用同一个数组,当然变化。
- 在创建 Child 的实例时,不能向Parent传参,只能给Child构造函数传参。 - Child.prototype被改写了,Child.prototype少了默认存在的constructor,而Parent实例的原型的constructor指向Parent。 对于第三点解释一下 ![](https://user-gold-cdn.xitu.io/2020/5/13/1720c184a6dd055d?w=397&h=501&f=png&s=24206) 图中child1是继承Parent的Child的实例,parent1就是普通Parent的实例,可以看到,child1._proto_中缺少了constructor。child1.constructor,通过原型链查找,会指向Parent。 给出完整的指向图 ![](https://user-gold-cdn.xitu.io/2019/12/9/16eea6954a257232?w=935&h=632&f=png&s=26408) 太丑了!!! 不过能看懂对吧,直男是没有审美的 ## 2.借用构造函数继承 ```js function Parent () { this.names = ['kevin', 'daisy']; this.fn = function(){ console.log(this.names) } } Parent.prototype.sayName = function() { console.log(this.names,'sayName') } function Child () { // 让Parent中的this(指向Parent的实例)指向Child的this(指向Child的实例),也就是Parent中的this指向Child的实例,在child实例上直接定义属性,而不是在原型上,实现继承 Parent.call(this); } var child1 = new Child(); // {names: ['kevin', 'daisy']} 因为Parent中的this指向Child的实例,也就是child1,所以names直接声明在child1上 child1.names.push('yayu'); console.log(child1.names); // ["kevin", "daisy", "yayu"] var child2 = new Child(); console.log(child2.names); // ["kevin", "daisy"],names都是分别声明在实例上,不是同一个数组的引用
避免了引用类型的属性被所有实例共享 ,直接将引用类型的属性在实例上声明,没有共享。
可以在 Child 中向 Parent 传参,call调用的时候传值
constructor指向也没问题了
可以说是解决的原型链继承的所有问题!
只能继承父类的实例属性和方法,不能继承原型上的属性/方法(Parent.prototype.sayName没有被继承)
定义在父类实例上的方法fn不是复用,是每次都创建新方法,不是最优方案
图里有个错误,Child.prototype._proto_并不是指向Parent.prototype,嘿嘿
原型链继承和借用构造函数继承一起用,原型链实现对原型属性和方法的继承,借用构造函数技术来实现实例属性的继承
function Parent (name) { this.name = name; this.colors = ['red', 'blue', 'green']; } Parent.prototype.getName = function () { console.log(this.name) } function Child (name, age) { Parent.call(this, name); this.age = age; } Child.prototype = new Parent(); Child.prototype.constructor = Child; var child1 = new Child('kevin', '18'); child1.colors.push('black'); console.log(child1.name); // kevin console.log(child1.age); // 18 console.log(child1.colors); // ["red", "blue", "green", "black"] var child2 = new Child('daisy', '20'); console.log(child2.name); // daisy console.log(child2.age); // 20 console.log(child2.colors); // ["red", "blue", "green"]
融合原型链继承和构造函数的优点
解决了原型链继承和构造函数两种方式的所有缺点
(单独的原型拷贝不能继承声明在实例上的属性,就是Person中的name,age,sex属性,这里不单独介绍了)
function Person(name,age,sex){ this.name = name; this.age = age; this.sex = sex; } Person.prototype.eat = function(){ console.log("正在吃饭") } Person.prototype.arr = [1,2] Person.prototype.sleep = function(){ console.log("正在睡觉") } function Man(larynx,beard,name,age,sex,){ Person.call(this,name,age,sex) this.larynx = larynx; this.beard = beard; } for(var key in Person.prototype){ Man.prototype[key] = Person.prototype[key] } Man.prototype.work = function(){ console.log('111') } var lzn = new Man(10,20); lzn.arr.push(90) console.log(lzn); // lzn.arr [1,2,90] var p1 = new Person() console.log(p1) // p1.arr[1,2,90] !!!
这个缺点很多继承方式都有,那这些继承的方式都不能用吗?
答案当然是能用的,只要我们保持一个原则,可能要修改的引用数据类型都定义在实例上,避免定义在原型上就好了。
例子:
function Person(name){ this.name = name; this.arr = [1,2,3] // 定义到实例上不就完事了 ? } function Man(name){ Person.call(this,name) } for(var key in Person.prototype){ Man.prototype[key] = Person.prototype[key] } var lzn = new Man(10,20); lzn.arr.push(4) console.log(lzn.arr) // [1,2,3,4] var p1 = new Person() console.log(p1.arr) //[1,2,3]
这样来看的话,这个方式应该是比较完美的了。
// 利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。 function object(obj){ function F(){} F.prototype = obj; // 直接赋值F.prototype, F.prototype.constructor没了 return new F(); } // object()对传入其中的对象执行了一次浅复制,将构造函数F的原型直接指向传入的对象。 var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); // anotherPerson /* { name: 'Greg', _proto_:{ friends:["Shelby", "Court", "Van","Rob"], name: 'Nicholas' } } */ var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends); //["Shelby","Court","Van","Rob","Barbie"]
另外,ES5中存在Object.create()的方法,能够代替上面的object方法。
说到这里,总结一下大部分继承方式可能会有的缺点:
// 结合借用构造函数传递参数和寄生模式实现继承 function inheritPrototype(subType, superType){ var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本 prototype.constructor = subType; // 增强对象,弥补因重写原型而失去的默认的constructor 属性 subType.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型 } // 父类初始化实例属性和原型属性 function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; // 借用构造函数传递增强子类实例属性(支持传参和避免篡改) function SubType(name, age){ SuperType.call(this, name); this.age = age; } // 将父类原型指向子类 inheritPrototype(SubType, SuperType); // 新增子类原型属性 SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType("xyc", 23); var instance2 = new SubType("lxy", 23); instance1.colors.push("2"); // ["red", "blue", "green", "2"] instance1.colors.push("3"); // ["red", "blue", "green", "3"]
这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上创建不必要的、多余的属性。
于此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()
这是最成熟的方法,也是现在库实现的方法,终于找到一个完善的继承方案了。。。
function Parent(value) { this.val = value } Parent.prototype.getValue = function() { console.log(this.val) } function Child(value) { Parent.call(this, value) } Child.prototype = Object.create(Parent.prototype, { constructor: { value: Child, enumerable: false, writable: true, configurable: true } }) const child = new Child(1) child.getValue() // 1 child instanceof Parent // true
简便了一下写法,原理跟上面的一样。
来了来了,终于来了
class Person{ constructor(name,age,sex){ this.name = name; this.age = age; } eat(){} sleep(){} } class Man extends Person{ constructor(name,age,sex){ //实现继承必须使用super,相当于调用了Person的constructor super(name,age); this.sex = sex; } work(){} } var lzn = new Man('lzn',18,'1') console.log(lzn) /* { age: 18, name: "lzn", sex: "1", __proto__: { constructor: Man, work:function(){}, __proto__: { constructor: Person, eat: function (){}, sleep: function() {} } } } */
还是ES6的继承香!
ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.call(this))。
ES6的继承有所不同,实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this。因为子类没有自己的this对象,所以必须先调用父类的super()方法,否则新建实例报错。
《高程》在这部分原型,原型链,继承写的非常清楚,第6章,建议多看几遍,每一遍都有收获。
参考博客 https://github.com/mqyqingfeng/Blog/issues/16
No description provided.
1.原型链继承
缺点:
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
child1.names.push('yayu'); // 修改child1的names属性,child1自身没有这个属性,会去其原型对象上查找,修改了原型对象上的names引用的那个数组 // 注意,names不在child1上,而是在child1的原型对象上 console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy", "yayu"] // 这个也好理解,Child.prototype是 Parent的实例,Parent函数中的this自然指向Child.prototype,相当于在Child.prototype中声明names属性并赋值一个数组,Child的实例上并没有names属性,都是共享同一个原型对象上的names属性,而实例child1修改Child原型上的引用数据类型的变量,Chlid实例的names属性引用同一个数组,当然变化。
优点
避免了引用类型的属性被所有实例共享 ,直接将引用类型的属性在实例上声明,没有共享。
可以在 Child 中向 Parent 传参,call调用的时候传值
constructor指向也没问题了
可以说是解决的原型链继承的所有问题!
缺点:
只能继承父类的实例属性和方法,不能继承原型上的属性/方法(Parent.prototype.sayName没有被继承)
定义在父类实例上的方法fn不是复用,是每次都创建新方法,不是最优方案
图里有个错误,Child.prototype._proto_并不是指向Parent.prototype,嘿嘿
3.组合继承
原型链继承和借用构造函数继承一起用,原型链实现对原型属性和方法的继承,借用构造函数技术来实现实例属性的继承
优点:
融合原型链继承和构造函数的优点
解决了原型链继承和构造函数两种方式的所有缺点
缺点:
4.原型拷贝+借用构造继承
(单独的原型拷贝不能继承声明在实例上的属性,就是Person中的name,age,sex属性,这里不单独介绍了)
缺点:
这个缺点很多继承方式都有,那这些继承的方式都不能用吗?
答案当然是能用的,只要我们保持一个原则,可能要修改的引用数据类型都定义在实例上,避免定义在原型上就好了。
例子:
这样来看的话,这个方式应该是比较完美的了。
5.原型式继承
缺点:
另外,ES5中存在Object.create()的方法,能够代替上面的object方法。
说到这里,总结一下大部分继承方式可能会有的缺点:
6.寄生组合式继承
这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上创建不必要的、多余的属性。
于此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()
这是最成熟的方法,也是现在库实现的方法,终于找到一个完善的继承方案了。。。
6. 寄生组合(简写)
简便了一下写法,原理跟上面的一样。
7.ES6 的class继承
来了来了,终于来了
还是ES6的继承香!
ES5继承和ES6继承的区别
ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.call(this))。
ES6的继承有所不同,实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this。因为子类没有自己的this对象,所以必须先调用父类的super()方法,否则新建实例报错。
参考部分
《高程》在这部分原型,原型链,继承写的非常清楚,第6章,建议多看几遍,每一遍都有收获。
参考博客 https://github.com/mqyqingfeng/Blog/issues/16