面不面试都要会的继承与原型链:原型链的尽头是null?
tips:每个技术点都值得优学优写:9期
好文推荐:
约2万字-Vue源码解读汇总篇(续更)
前端要会打组合拳,复盘30+技术点打出的功能
前言
我们知道,es 有一个关键字叫 class ,这是在 es2015(es6) 的时候引入的。它的中文意思是“类”, 在像 java 这样的编程语言中, 原生就有类(class)的支持,但 es6 引入的 class 关键字, 也只是语法糖,JavaScript 仍然是基于原型的。
当谈到继承时,JavaScript 只有一种结构:对象。 每个实例对象(object)都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
继承属性与原型链
javaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
在原型链上,属性的被传递拥有,就是一种继承现象。(使用继承关键字 extends 的继承暂且不谈)
下面通过一个示例来理解继承与原型链
// 使用语法结构创建的对象 let obj = { a: 'hello', fc: function () { return this.a + 1 } } // obj 这个对象继承了 Object.prototype 上面的所有属性 // obj 自身没有名为 hasOwnProperty 的属性 // hasOwnProperty 是 Object.prototype 的属性 // 因此 obj 继承了 Object.prototype 的 hasOwnProperty // Object.prototype 的原型为 null // 原型链如下: // obj ---> Object.prototype ---> null console.log(obj) console.log(obj.__proto__) console.log(obj.__proto__.__proto__) // null,这就是原型链的终点 let subObj = Object.create(obj); // 创建的 subObj 本身没有自身的属性,但它继承有 obj 的属性。 console.log(subObj.fc()); // 3,fc() 继承自 obj // 那么此时,我们就说 subObj 是一个继承自 obj 的对象,因为继承,obj 的一些属性被传递给了 subObj, // 例如 fc() 就继承自 obj // subObj 的原型链是 // subObj ---> obj ---> Object.prototype ---> null console.log(subObj)
这是一张打印上面 obj 对象的原型链截图, 打印结果也符合上面关于 obj 对象原型链的结论:obj ---> Object.prototype ---> null
下面是一张打印 subObj 的原型连截图: 打印结果也符合上面关于 subObj 对象原型链的结论:subObj ---> obj ---> Object.prototype ---> null
创建对象和生成原型链的几种方法
使用 Object.create 创建的对象
Object.create() 是 es5 引入的创建对象的方法,使用该方法创建的新对象的原型就是 Object.create() 传入的第一个参数。
下面是一个例子。
let a = {a: 1};// 原型链: a ---> Object.prototype ---> nulllet b = Object.create(a);// 原型链:b ---> a ---> Object.prototype ---> nullconsole.log(b.a); // 1 ,b并没有自身属性,b.a 属性是继承而来的let c = Object.create(b);// 原型链:c ---> b ---> a ---> Object.prototype ---> nulllet d = Object.create(null);// 原型链:d ---> null // 根据定义,null 没有原型,因此 null 就是原型链的最后一个环节console.log(d.hasOwnProperty); // undefined, 因为 d 没有继承 Object.prototype,所以没有 hasOwnProperty 属性。
使用语法结构创建的对象
例如像下面这样,没用通过关键字创建的
let obj = {a: 1};// obj 这个对象继承了 Object.prototype 上面的所有属性// obj 自身没有名为 hasOwnProperty 的属性// hasOwnProperty 是 Object.prototype 的属性// 因此 obj 继承了 Object.prototype 的 hasOwnProperty// Object.prototype 的原型为 null// 原型链如下:// obj ---> Object.prototype ---> null
使用构造器创建的对象
在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。
下面是一个示例
function Obj() { this.arr = []; this.arr2 = [];}Obj.prototype = { addArr: function(v){ this.arr.push(v); }};let g = new Obj();// g 是生成的对象,他的自身属性有 'arr' 和 'arr2'。// 在 g 被实例化时,g.[[Prototype]] 指向了 Obj.prototype。// 原型链如下:// g ---> obj ---> Object.prototype ---> null
使用 class 关键字创建的对象
ES6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。 JavaScript 仍然基于原型。这些新的关键字包括 class, constructor,static,extends 和 super。
看见了吗? js 不仅有 class(类) 关键字,还有 extends(继承) 关键字。
下面是一个来自 Mozilla 的示例
class Polygon { constructor(height, width) { this.height = height; this.width = width; }}class Square extends Polygon { constructor(sideLength) { super(sideLength, sideLength); } get area() { return this.height * this.width; } set sideLength(newLength) { this.height = newLength; this.width = newLength; }}let square = new Square(2);
关于性能
在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。
遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性, 则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty方法。
小结
JavaScript 仍然是基于原型的,尽管有 class 和 extends 等关键字。
没错,原型链的最后一个环节是 null,null 是原型链的终点。
理解原型继承模型对于深入理解 JavaScript 是至关重要的,它是 JavaScript 的高级部分内容。
有一种继承是基于原型链的继承,这种继承当然也具有获得被继承者的属性的能力。
原文:https://juejin.cn/post/7099460734924357645