在JS中初始化一个实例的时候,会调用new去完成实例化,那么new函数到底干了些什么事情,javascript
此外,咱们都知道在chrome,firefox等浏览器中,实例化的对象通常都经过 __proto__指向构造函数的原型,因此会有一条以下的对应关系java
function Person () {}
var p = new Person()
p.__proto__ = Person.prototype
复制代码
因此咱们能够实现最简单的new的山寨函数chrome
function mockNew() {
var obj = new Object()
//获取构造函数
var Constructor = [].shift.call(arguments)
//绑定原形链
obj.__proto__ = Constructor.protoype
//调用构造函数
Constructor.apply(obj,arguments)
return obj
}
复制代码
经过以上方法,咱们就山寨了一个最简单的new方法,这个山寨的new方法能够更好的帮咱们去理解继承的所有过程。数组
首先咱们都知道原形链继承会存在必定的问题,红宝书上说的很清楚,这种继承方式会产生两个问题浏览器
第一个缘由很清楚也很容易解决,那么为何会专门对引用类型产生问题呢,仍是先上代码bash
//父类Animal
function Animal() {
this.name = 'animal'
this.food = ['food']
}
Animal.prototype = {
constructor: Animal,
//更改name
setName: function(name) {
this.name = name
},
//更改food
giveFood: function(food) {
this.food.push(food)
}
}
//子类AnimalChild
function AnimalChild() {}
//绑定原形链
AnimalChild.prototype = new Animal()
let cat = new AnimalChild()
let dog = new AnimalChild()
cat.setName('cat')
cat.giveFood('fish')
console.log(cat.name) //cat 输出cat很正常
console.log(dog.name) //animal 这个就有点神奇了
console.log(cat.food) // ['food','fish']
console.log(dog.food) // ['food','fish']
复制代码
经过以上的例子,咱们发现原形式继承并非全部的数据都会共享,产生影响的数据只有引用类型的,这个的缘由是为何呢,咱们来使用咱们的山寨new方法回顾整个过程app
//实例话父类,绑定到子类的原形链上
AnimalChild.prototype = mockNew(Animal)
复制代码
当咱们这么调用的时候,回顾一下mocknew的执行过程,其中会执行这样一步函数
//在构造的过程当中,子类会调用父类构造函数
Animal.apply(obj,arguments)
复制代码
这步的执行,会致使自己在父类构造函数中的this.name被绑定到了一个新的函数上,由于最终的返回值被复制到子类型的protoype上,因此,子类的protoye长得是如下模样优化
//打印子类的prototype
console.log(AnimalChild.prototype)
//打印结果
AnimalChild.prototype = {
name: 'animal',
food: ['food'],
__proto__: {
constructor: Animal,
setName: function() {},
giveFood: function() {}
}
}
复制代码
能够看出借由new方法,父类构造函数中的变量绑在在子类的原形上(prototype),而父类的原形绑在了子类原形的原形实例上(prototype.proto )ui
紧接着咱们在实例化子类型实例
var cat = mockNew(animalType)
复制代码
这步咱们会修改cat对象的__proto__属性,最终生成的cat实例打印以下
console.log(cat)
//打印的结果
cat = {
__proto__: {
name: 'animal',
food: ['food'],
__proto__: {
constructor: Animal,
setName: function() {},
giveFood: function() {}
}
}
}
复制代码
能够看出全部父类型的变量都被绑定在了实例的原形上,那为何引用类型的数据类型会产生错误呢,这其实和引用类型的修改方式有关
当咱们修改name的时候,函数会主动在对象自己去赋值,及
cat.name = 'cat'
console.log(cat)
//打印结果
cat = {
//绑定在对象上,而不是原形链
name: cat
__proto__: {
name: 'animal',
food: ['food'],
//.....
}
}
复制代码
而当咱们对引用类型的数组进行操做的时候,函数会优先找函数自己时候有这个变量,若是没有的话,回去原形链上找
cat.name.push('fish')
console.log(cat)
//打印结果
cat = {
__proto__: {
name: 'animal',
//在原形链上修改
food: ['food','fish'],
//.....
}
}
复制代码
虽说原形式继承会带来问题,可是实现的思路是很是有用的,对于父类的方法,变量,通通放在原形链上,继承的时候,将同名的内容通通覆盖,放在对象自己,这样就解决了函数的继承和内容的重写
基于此,寄生组合的方法获得重视,下面分析如下执行过程,依然是使用上面的Animal和AnimalChild类
//寄生组合式方法调用
//声明父类
function Animal() {
this.name = 'animal'
this.food = ['food']
}
Animal.prototype = {
constructor: Animal,
//更改name
setName: function(name) {
this.name = name
},
//更改food
giveFood: function(food) {
this.food.push(food)
}
}
//开始继承
function AnimalChild() {
Animal.call(this)
}
function f() {}
f.prototype = Animal.prototype
var prototype = new f()
prototype.constructor = AnimalChild
AnimalChild.prototype = prototype
复制代码
上述代码和原形式继承主要有两点区别
首先说一下第一点,调用call,调用call以后,至关于在子类的构造函数内部执行了一变父类的构造函数,这个时候,父函数内部经过this声明得一些属性都转嫁到了子函数的构造函数中,这样就解决了原形式继承中变量共享的问题
其次,下面的prototype赋值方法带有一点优化的属性,由于父类构造函数中的内容经过call已经所有拿到了,只须要再将原形绑定就能够了,此外,经过new的方式,子类的挂在原形链上的方法其实是和父类原形方法跨层级的
//为子类添加原形方法
AnimalChild.prototype.childMethod = function() {}
console.log(AnimalChild.prototype)
//打印结果
AnimalChild.prototype = {
construcor: AnimalChild,
//绑定在原形上
childMethod: function() {},
//父类的原形方法都在这里
__proto__: {
setName: function() {},
giveFood: function() {}
}
}
复制代码
经过寄生组合式继承咱们能够获得以下结论,加入B继承了A,那么能够获得一个等式
B.prototype.__proto__ = A.prototype
复制代码
知足这个等式的话其实咱们就能够说B继承了A的原形连接
在ES6中的super效果下,其实实现了两条等式
B.__proto__ = A
//原形链相等
B.prototype.__proto__ = A.prototype
复制代码
第二条等式咱们理解,那么第一条等式是什么意思呢,在寄生组合式继承中,咱们使用call的方式去调用父类构造函数,而在ES6中,咱们能够理解为 子类的构造函数是基于父类实例的加工,super返回的是一个父类的实例,这样也就解释了等式一之间的关系。
固然,ES6中的实现方法更为优雅,借由一个ES6中提供的Api:setPrototypeOf,能够用以下方式实现
class A {}
class B {}
//原形继承
Object.setPrototypeOf(B.prototype, A.prototype);
//构造函数继承
Object.setPrototypeOf(B, A);
复制代码
仔细的总结了如下以后,发现对于JS更了解了!