温故而知新:
js中构造函数,原型,实例的关系,原型链是什么
js/javascript 继承方式主要有哪些?及其优缺点特点
// 原型式
// 概念: 使用已存在的对象创建一个新的对象,而不必创建新的自定义对象类型
// 实现方法
function inherit(father) {
function Tmp() {}
// 继承父类的对象(直接把父对象当原型对象)
Tmp.prototype = father
return new Tmp()
/*
回顾new的过程
1.创建了新的对象,假设为o
2.通过apply,call或者bind方法把this指向o(在o的环境中执行tmp构造函数)
3.把tmp里面的属性都给绑定在o上
4.返回o的地址
*/
}
// 其实可以看出过程和原型链继承很相似
// 只是在tmp上没有属性,而tmp就相当于子类
// 使用
const father = {
name: '张三',
attribute: ['勇敢','打球','男']
}
// 使用已存在的对象fahter,创建了一个新的对象
// 而没有声明新的自定义对象类型
const son = inherit(father)
const son2 = inherit(father)
console.log(son.name) // 张三
console.log(son2.name) // 张三
// 由于Tmp中没有name属性通过原型链,获取到父亲的name属性
son.name = '儿子1'
son2.name = '儿子2'
// 在son,son2上添加属性name并赋予初始值
console.log(son.name) // 儿子1
console.log(son2.name) // 儿子2
console.log(son.attribute) // ["勇敢", "打球", "男"]
console.log(son2.attribute) // ["勇敢", "打球", "男"]
son.attribute.push('聪明')
// 通过原型链访问父类的attribute属性
console.log(son.attribute) // ["勇敢", "打球", "男", "聪明"]
son.attribute.push('乐观')
console.log(son2.attribute) // ["勇敢", "打球", "男", "聪明", "乐观"]
console.log(father.name) // 张三
console.log(father.attribute) // ["勇敢", "打球", "男", "聪明", "乐观"]
// 由于attribute是引用类型,attribute保存的是内容的地址
// 所以他们对内容进行了修改,但是attribute始终没有变
// 指向的是父亲的attribute,所以内容都会变动
// 寄生式继承
function inherit(father) {
function Tmp() {}
Tmp.prototype = father
return new Tmp()
}
const father = {
name: '张三',
attribute: ['勇敢','打球','男']
}
function makePeople(father) {
const son = new inherit(father)
// 理解上面一步是关键,其实有些像构造函数继承
// 简洁地说明一下由于new的中return了
// new的最后一步:返回新生成的对象。
// 如果被调用的函数没有显式的 return 表达式(仅限于返回对象),
// 则隐式的会返回this对象就是新创建的对象。
// 所以new inherit(father) 和 直接调用的结果一样
// 都是Tmp的实例
// 但是inheri内部的this是不一样的
// new inherit的话this是inherit实例对象,直接调用是window
son.newAttribute = []
son.englishName = ''
son.skill = function(){}
return son
}
// 可以看出每次生成一个子类都会在上面挂一个同样的方法
// 从而导致不能复用
// 寄生组合式继承
// 组合式继承中通过构造函数继承了属性
// 那么在方法(原型)上面的继承又调用了一次构造函数是没必要的
// 而实例通过原型链访问到的是构造函数的原型对象(关键)
// 上面的意思就是实例通过__proto__访问到的是构造函数的原型对象
// 即我们需要的是一个父类原型对象的复制给子类当作原型对象就行了
//
//
function inherit(father) {
function Tmp() {}
Tmp.prototype = father
return new Tmp()
}
function inheritPrototype(father, son) {
// 复制父类原型对象,准备当作子类的原型对象
const t = inherit(fahter.prototype)
// t的原型对象是父类的原型对象,所以constructor指向父类的构造函数
// 把子类的constructor重新指向子类构造函数
t.constructor = son
// 把父类原型对象复制体当作子类原型对象
son.prototype = t
}
// 使用
function F(name) {
this.name = name
this.arr = ['1']
}
F.prototype.f = function() {
console.log('father')
}
function S(name, other) {
F.call(this, name)
this.other = other
}
inheritPrototype(S, F)
const t = new S('qq', 2000)
t是S的实例,S的原型对象是父类F的原型对象的副本
所以相当于是t.__proto__ --> F的原型对象实现了原型链上的继承
也没有第二次调用F函数
子类原型对象被赋了一个父类原型对象的引用
注意: 如果子类想添加原型方法必须通过原型对象点语法一个一个添加,
不能通过对象赋值不然会覆盖父类原型对象的引用
继承原理
构造函数可以通过prototype访问到原型对象
子类原型对象通过constructor访问到子类构造函数
子类实例可以通过__proto__访问到子类构造函数原型对象
可以参考顶部的文章内的图片和内容理解