直接先贴题目吧
function A() {
this.name = 'a'
this.color = ['green', 'yellow']
}
function B() {
}
B.prototype = new A()
var b1 = new B()
var b2 = new B()
b1.name = 'change'
b1.color.push('black')
console.log('b1', b1)
console.log('b2', b2)
console.log(b1.name) // change
console.log(b2.name) // a
console.log(b1.color) // ['green', 'yellow', 'black']
console.log(b2.color) // ['green', 'yellow', 'black']
这其实就是我们的常见继承模式之一,原型继承,为何会出现这样的情况呢?
最大的疑惑是,为何两个实例对象b1,b2里面的color属性都被修改了?
又为何b1.name = change 而b2.name 却没有发生改变。
首先,你得有原型链继承的知识点,有了这个知识点后,我们再理解下我们经常挂在嘴边的基本数据类型和
引用数据类型,他们的存储方式和读取方式有何异同,带着这一些疑惑,我们再看下栈内存和堆的概念
看了上图,一目了然的看到,不管是基本数据类型还是引用类型,实际都是存在栈内存的,只不过引用数据类型还会指向一个具体的堆。他们的区别我简单就阐述这么一些。
B.prototype = new A()
上面这段代码到底发生了什么?实际考察的就是new一个构造函数到底发生了什么?这里结合例子再梳理一遍。
a、 A构造函数的this赋值给B.prototype即可 B.prototype = this
b、 B.prototype__proto__ = A.prototype
注意A.prototype这里指向的是Object
c、 A.call(this)
间接调用A函数,将构造函数的原始属性和方法赋值给B.prototype这个对象
d、 return this 也就是return B.prototype
此时我们直接打印下以下代码
function A() {
console.log('this_A', this)
this.name = 'a'
this.color = ['green', 'yellow']
}
function B() {
console.log('this_B', this)
}
B.prototype = new A()
var b1 = new B()
再剖析一开始贴出的代码是如何执行或者指向的。
首先:b1和b2实例化是存在栈内存里面,然后指向了堆内存的两个对象:
因为指向的是两个对象,所以他们是两个不同的实例,各个属性和方法都是特有独立的。
但这两个对象的原型指向了同一个实例对象:
两个对象的原型共享了一个实例对象,共享的意思是指:
A的实例上的原始方法和属性以及A实例的原型都是共享的
b1.name = 'change'
实际是在b1的实例对象增加一个属性name,并将name属性赋值为change
由于当分析的b1和b2是两个不同的实例,所以b1的name赋值并不会影响到b2里面的原始属性情况
b1.color.push('black')
在b1的color数组里push了一个black方法,注意是push而不是赋值。
因为是在b1里的color里push,所以会先去b1实例里去找color,发现并没有color,
所以进一步通过原型链找到了b1.__proto__原型 也就是B.prototype.
而前面分析了B.prototype里共享了A的实例上的原始方法和属性以及A实例的原型
此时正好在A实例上的原始属性里找到了color,所以push成功,停止原型链搜索。
又因为color是一个引用属性
一开始的栈和堆知识应用进来理解
color指向了一个数组,所以b1.color.push('black')
不仅改变了b1和A的实例,同时还改变了b2
为何改变了b2呢?
同样运用原型链知识
b2.color先去构造函数里找color,没找到,就沿着原型链找到了(具体看图),就这么简单哈。
这其实是继承的一种方法叫做原型链继承
那么如何规避color属性被指向同一个引用类型的问题呢?
实际我们上面就是运用到继承里面的一个原型链继承的方法。
还有一种继承是构造函数继承
稍微修改下:
function B() {
A.call(this)
}
经过改动后,我们每次在实例化B的时候,会将实例化对象的引用对象作为参数传递到B这个构造函数,在间接调用A函数的时候,也修改了A执行的时候this的执行问题。此时this的执行不再是window而是实例化的对象b1或b2.
分析下,我们不写A.call(this)这句代码的时候,A的实例对象实际就是指向A实例对象的本身,这句话理解起来有点傲,执行了A.call(this)后,这里翻译过来我想应该是这样的,不知道是否有错?
调用A,而不是实例化A,即A(),但此时A的this并不是window对象,这里没有实例A所以,this不是A实例化指向的那个引用对象。然后我们认为的修改了A这个函数执行时执行上下文的this执行,这个this此时成了b1或者b2实例对象。
此时我们再修改b1.color的时候,同样会去原型上去找color熟悉,但此时原型链上的this已经指向了b1这个实例化对象,所以当我们修改b1.color的时候,实际只修改了b1这个实例化对象对应原型上的那个对象。而b2.color并不会被改变。侧重理解this的指向问题
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。