JavaScript 不包含传统的类继承模型,而是使用 prototypal 原型模型。
虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大。实现传统的类继承模型是很简单,但是实现 JavaScript 中的原型继承则要困难的多。
由于 JavaScript 是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的,今天我们就来了解一下原型和原型链。
var myObj = {}; function myFunc() { } console.log(myObj.prototype); // undefined console.log(myFunc.prototype); // myFunc {}
通过 prototype 我们可以动态的向对象添加属性和方法,并且是可以继承的。
function myFunc(val) { this.val = val; } myFunc.prototype.getVal = function() { return this.val; }; var myInstance = new myFunc(1); // myInstance 调用原型属性 console.log(myInstance.getVal()); // 1 // 访问 myInstance 的原型 console.log(Object.getPrototypeOf(myInstance)); // myFunc { getVal: [Function] } // 为 myInstance 添加属性 val myInstance.val = 100; // getVal 中的 this 指向 myInstance 实例 console.log(myInstance.getVal()); // 100
当继承的函数 getVal 被调用时, this 指向 myInstance 实例, 而不是原型 myFunc。
原型链
根据 ECMAScript 标准,someObject.[[Prototype]] 符号是用于指派 someObject 的原型。
这个等同于 JavaScript 的 proto 属性(现已弃用)。
从 ECMAScript 6 开始, [[Prototype]] 可以用 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问。
当一个对象访问一个属性时,查找的顺序是:
该对象自身属性 =》 该对象的原型的属性 =》 该对象的原型的原型属性 ... 直到找到匹配的属性或到达原型链末端(prototype 为 undefined)
var objA = function () { this.a = 'a'; this.b = 'b'; }; var myInstance = new objA(); myInstance.a = 'a in myInstance'; // myInstance 自身的属性有 a, 访问并停止查找属性 console.log(myInstance.a); // a in myInstance // myInstance 自身的属性没有 b, 访问 myInstance 的原型 objA, objA 有属性 b, 访问并停止查找属性 console.log(myInstance.b); // b // myInstance 自身和原型链上每个原型都没有属性 c,结果为 undefined console.log(myInstance.c); // undefined
创建对象和原型链的几种方法
通过 JS 语法创建
var obj = { a: 1}; // 继承于 Object.prototype // 原型链:obj => Object.prototype => undefined console.log(Object.getPrototypeOf(obj));// {} var arr = [1]; // 继承于 Array.prototype // 原型链:arr => Array.prototype => undefined console.log(Object.getPrototypeOf(arr)); // [] function func() { } // 继承于 Function.prototype // 原型链:func => Function.prototype => undefined console.log(Object.getPrototypeOf(func)); // [Function]
通过构造函数创建
在 JavaScript 中, 用 new 操作符来作用与一个函数时,这个函数就是构造函数 constructor。
var func = function() { this.val = 1; }; func.prototype.getVal = function() { return this.val; }; var instance = new func(); // instance 是构造函数 func 的一个实例对象 // instance 自身具有 val 属性 // instance.[[Prototype]] 指向 func.prototype
通过 Object.create 创建
var a = { a: 'a' }; var b = Object.create(a); // b 继承于 a.prototype // 原型链:b => a => Object.prototype => undefined console.log(b.a) // a
通过 class 关键字 [ES6]
ES6 中的 class 只是基于原型继承的一种语法糖:
'use strict'; class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } } class Student extends Person { constructor(firstName, lastName) { super(firstName, lastName); } get name() { return this.firstName + ' ' + this.lastName; } set name(newFirstName) { this.firstName = newFirstName; } } var me = new Student('Dog', 'Dong'); console.log(me.name); // Dog Dong me.name = 'Dog2'; console.log(me.name); // Dog2 Dong
其他
hasOwnProperty()
检测某个对象自身(不含原型链)是否含有某属性。
var objA = { valA: 'val a' }; var objB = Object.create(objA); objB.valB = 'val b'; console.log(objA.hasOwnProperty('valA')); // true console.log(objB.hasOwnProperty('valA')); // false console.log(objA.hasOwnProperty('valB')); // false console.log(objB.hasOwnProperty('valB')); // true
在原型链上查找属性是很耗时的,尤其是访问一个不存在的属性时,整个原型链会被遍历。
当我们只想访问对象自身属性时,可以通过 hasOwnProperty 来判断对象是否有该属性,避免遍历原型链。
Object.getPrototypeOf() [ES6]
返回对象的原型。
var objA = { valA: 'val a' }; var objB = Object.create(objA); console.log(Object.getPrototypeOf(objB)); // { valA: 'val a' }
Object.setPrototypeOf() [ES6]
设置对象的原型。
var objA = { valA: 'val a'}; var objB = {}; Object.setPrototypeOf(objB, objA); console.log(Object.getPrototypeOf(objB)); // { valA: 'val a' }