一道简单的js继承面试题来查考你是否真的透彻的了解继承

直接先贴题目吧

 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 却没有发生改变。
首先,你得有原型链继承的知识点,有了这个知识点后,我们再理解下我们经常挂在嘴边的基本数据类型和
引用数据类型,他们的存储方式和读取方式有何异同,带着这一些疑惑,我们再看下栈内存的概念

看了上图,一目了然的看到,不管是基本数据类型还是引用类型,实际都是存在栈内存的,只不过引用数据类型还会指向一个具体的堆。他们的区别我简单就阐述这么一些。

再剖析上面的代码是如何执行或者指向的。
首先:b1和b2实例化是存在栈内存里面,然后指向了堆内存的两个对象。但这两个对象的原型指向了同一个实例对象,这个实例对象同样是存在栈内存中的(然后指向了一个对象)。所以简而言之,b1和b2的原型对象指向了同一个实例对象(A的实例对象)。

b1.name = 'change'

实际是在b1的实例对象增加一个属性name,并将name属性赋值为change,但它并没有修改原型链上的name属性。

b1.color.push('black')

这里涉及到原型链向上查找属性的知识点,实例对象b1里面并没有color属性,于是去原型链上寻找,结果在A的实例对象找到了color,但此时的color属性指向的是一个引用类型,而b1和b2都继承于A这个实例对象,根据引用类型color指向一个堆数组来看,当b1修改了原型上的color属性,实际也就修改了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的指向问题

你可能感兴趣的:(javascript,原型链)