原型链
原型链的概念
在JavaScript中,每一个构造函数都有一个原型,这个原型中有一个属性constructor
会再次指回这个构造函数,这个构造函数所创造的实例对象,会有一个指针(也就是我们说的隐式原型__proto__
或者是浏览器中显示的[[Prototype]]
)指向这个构造函数的原型对象。如果说该构造函数的原型对象也是由另外一个构造函数所创造的实例,那么该构造函数的原型对象也会存在一个指针指向另外一个构造函数的原型对象,周而复始,就形成了一条原型链。 最特别的是所有的没有经过再继承函数都是由Function
实例化来的,所有的除了函数外的对象都是由Object
实例化来的,其中Object
也是由Function
实例化来的,但是Object.prototype.__proto__ === null
是成立的。
再强调一遍:原型链是沿着对象的隐式原型一层层的去寻找的,找到的是构造函数所创造的实例。例如下:
这个就是相当于由Student
new 出来的实例s
,查找自身的 name
属性,然后沿着原型链查找,找到Student
中的prototype
当中,然后找到了name
这个属性。
而这个例子,由红框框起来的代码(寄生继承的关键代码),代替注释掉的部分,最终s
是找不到name
属性的,这是因为红框中的代码,仅仅是将Student
的隐式原型指向了Person
的显示原型对象,未能创建任何的实例,当然就不会存在属性这个说法。
原型链的问题
原型链的问题主要有两个方面,第一个问题是,当原型中出现包含引用值(比如数组)的时候,所有在这条原型链中的实例会共享这个属性,造成“一发而动全身”的问题。第二个问题就是子类在实例化时,不能够给父类型的构造函数传参,即
无法在不影响所有对象实例的情况下把参数传递进父类型的构造函数传参
几种常见的继承方法
盗用构造函数
function SuperType() { this.friends = ['张三','李四'] } function SubType() { SuperType.call(this); } const p1 = new SubType(); p1.friends.push('王武'); const p2 = new SubType(); console.log(p2.friends); // ['张三','李四', '王武']
盗用构造函数实现继承在这个例子中有了充分的体现: 首先在子类的构造函数中调用父类的构造函数。因为毕竟函数就是特定上下文中执行代码的简单对象,所以可以使用call()
方法以创建的对象为上下文执行的构造函数。
盗用构造函数的主要问题,也是创建对象的几种方式中构造函数模式自定义类型的问题:必须在构造函数中定义方法,造成内存浪费。另外,子类也不能访问父类原型上定义的方法,因此,盗用构造函数也不会单独使用。
组合继承
组合继承也称为伪经典继承,综合了原型链和构造函数,将两者的有点结合起来。基本的思路就是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实现实例的属性。这样就可以把方法定义在原型上实现复用,又可以让每个实例有自己的属性。
function SuperType(name) { this.name = name; this.friends = ['张三','李四']; } SuperType.prototype.sayName = function() { console.log(this.name) } // 继承方法 SubType.prototype = new SuperType(); function SubType(name, age) { SuperType.call(this, name); this.age = age; } const p1 = new SubType('赵六', 12); const p2 = new SubType('赵六2', 22); // 创建的 p1 和 p2 能够拥有自己的属性并且引用值属性也是独立的,此外,每一个实例能够公用父类的方法。
组合继承已经接近完美了,但是,我们发现,实现组合继承就要调用两次父类构造函数。在本质上,子类型最终是要包含超类对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了,这就为减少一次调用父类构造函数提供了思路。
原型式继承
const person = { name: 'zs', friends: ['ls','ww'] } // 创造出一个实例,这个实例的隐式原型指向 person const anotherPerosn = Object.create(person); anotherPerosn.name = 'xm' anotherPerosn.friends.push('zl') console.log(anotherPerosn.name) // xm console.log(anotherPerosn.friends) // ['ls','ww', 'zl']; const anotherPerosn2 = Object.create(person); anotherPerosn.name = 'xh' anotherPerosn.friends.push('dd') console.log(anotherPerosn2.name) // xh console.log(anotherPerosn2.friends) // ['ls','ww', 'zl', 'dd'];
对于原型链继承就不再过多的解释了。。。。
寄生式继承
寄生式继承与原型式继承比较相似,都会存在属性引用值共享的问题。
function createAnotherPerson(original) { const clone = Object.create(original); //通过调用函数创建一个新的对象 clone.sayHi = function() { // 以某种方式增强这个对象 console.log('Hi'); } return clone; }
寄生式继承,不仅存在着属性引用值共享的问题而且函数还不能进行复用。
寄生组合式继承
// 实现了寄生式组合继承的核心逻辑 function inheritPrototype(subFn, parentFn){ subFn.prototype = Object.create(parentFn.prototype); // 创建赋值对象 Object.defineProperty(subFn.prototype,'constructor', { // 增强对象 enumerable: false, writable: false, configurable: false, value: subFn, }) } function Person(name, age, address) { this.name = name; this.age = age; this.address = address; } Person.prototype.eating = function() { console.log(this.name + "正在吃饭"); } // 共享方法 function Student(name, age, address, sno) { Person.call(this, name, age, address); // 绑定 this 确保创建出来的对象是相互独立的 this.sno = sno; this.studing = function() { console.log(`${this.name}正在学习`) } } function Teacher(name, age, address, tno) { Person.call(this, name, age, address) this.tno = tno; }
寄生+组合式(构造函数+原型链)完美的解决了其他继承出现的问题。
到此这篇关于JavaScript原型链及常见的继承方法的文章就介绍到这了,更多相关JS原型链内容请搜索阿兔在线工具以前的文章或继续浏览下面的相关文章希望大家以后多多支持阿兔在线工具!