详解JavaScript的继承和原型链(prototype)

  • Post author:
  • Post category:java




一、原型链 是什么?

JavaScript 的每个实例对象都有一个指向上一层对象的私有属性(称之为

__proto__

) ,上一层对象又指向再上一层对象,就这样层层向上直至

__proto__

属性为 null ,最后这个对象就是

Object

这种通过

__proto__

属性来链接对象的方法,称之为

原型链

var o = {a: 1};
// 原型链如下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null

function f() {
  this.a = 1;
  this.b = 2;
}
let o = new f();
f.prototype.b = 3;
f.prototype.c = 4;
// 原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null



二、怎么使用?


1、Object.getPrototypeOf() 和 Object.setPrototypeOf()

ECMAScript 6 开始,可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问

__proto__

属性。

function f() {
  this.a = 1;
  this.b = 2;
}

let o = new f();
// 1、为 f 的上层对象新增属性(推荐此种方式新增属性)
f.prototype.b = 3;
f.prototype.c = 4;

// 2、查看上层对象属性
console.log(Object.getPrototypeOf(o));

// 3、此种方式设置上层对象的属性,会覆盖掉所有旧属性,慎用
console.log(Object.setPrototypeOf(o, {e:5}));

// 4、查看新的上层对象属性
console.log(Object.getPrototypeOf(o));



2、prototype 与 Object.getPrototypeOf

两者功能一致,都是用来访问原型属性,区别是:

  • prototype 用于类,而 Object.getPrototypeOf() 用于实例;
  • 共享:所有实例都可以访问,新增的 prototype 属性。
function A(){
  this.a = "a";
}

let aIns = new A();

// 1、一个用于类型、一个用于实例
console.log(A.prototype);
console.log(Object.getPrototypeOf(aIns));

let bIns = new A();
// 2、共享:所有实例都可以访问 新增的原型属性
A.prototype.b = "b";
console.log(Object.getPrototypeOf(aIns).b);
console.log(Object.getPrototypeOf(bIns).b);
var o = new Foo();

// 上面代码是让 JavaScript 执行了下面代码

var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);



3、hasOwnProperty

当访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻原型链上的所有对象,最后判断属性是否存在。hasOwnProperty 是 JavaScript 中唯一一个不会遍历原型链的方法。

const object1 = {};
object1.property1 = 42;

console.log(object1.hasOwnProperty('property1'));
// expected output: true

console.log(object1.hasOwnProperty('toString'));
// expected output: false



4、undefined

没有赋值的属性是 undefined,所以无法通过 undefined 来判断属性是否存在。




三、继承

继承也是基于原型链的特性。ECMAScript6 引入 class 关键字来实现 类对象,但类对象也是基于原型链。



1、Object.create

用 Object.create 来创建继承对象。

var a = {a: 1};
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype



四、其他


1、性能

试图访问不存在的属性时会遍历整个原型链,在原型链上查找属性比较耗时,对性能有副作用。




2、错误实践

扩展内置原型会破坏封装,这不是好的解决方案,使用扩展内置原型的唯一理由是支持 JavaScript 引擎的新特性,如

Array.forEach

原生原型

不应该

被扩展,除非它是为了与新的 JavaScript 特性兼容。




五、参考文档



版权声明:本文为fanlehai原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。