Skip to content

原型继承

原型继承(Prototypal inheritance) 这个语言特性能够帮助我们实现这一需求。

  1. [[Prototype]]
    1. 在 JavaScript 中,对象有一个特殊的隐藏属性 [[Prototype]](如规范中所命名的),它要么为 null,要么就是对另一个对象的引用。该对象被称为“原型”:
    2. 从 object 中读取一个缺失的属性时,JavaScript 会自动从原型中获取该属性。在编程中,这被称为“原型继承”。
    3. 属性 [[Prototype]] 是内部的而且是隐藏的,但是这儿有很多设置它的方式。
    JavaScript
    let animal = {
      eats: true
    };
    let rabbit = {
      jumps: true
    };
    
    rabbit.__proto__ = animal; // 设置 rabbit.[[Prototype]] = animal
    //“`animal` 是 `rabbit` 的原型”,或者说 “`rabbit` 的原型是从 `animal` 继承而来的”。
	1. `__proto__` 是 `[[Prototype]]` 的因历史原因而留下来的 getter/setter
	2. `__proto__` 与内部的 `[[Prototype]]` **不一样**。`__proto__` 是 `[[Prototype]]` 的 getter/setter。
	
2. **写入不使用原型**
	1. 原型仅用于读取属性。对于写入/删除操作可以直接在对象上进行。
```JavaScript
	let animal = {
	  eats: true,
	  walk() {
	    /* rabbit 不会使用此方法 */
	  }
	};
	
	let rabbit = {
	  __proto__: animal
	};
	
	rabbit.walk = function() {
	  alert("Rabbit! Bounce-bounce!");
	};
	
	rabbit.walk(); // Rabbit! Bounce-bounce!
  1. “this” 的值
    1. 无论在哪里找到方法:在一个对象还是在原型中。在一个方法调用中,this 始终是点符号 . 前面的对象。
  2. for…in 循环
    1. for..in 循环也会迭代继承的属性。
JavaScript
	let animal = {
	  eats: true
	};
	
	let rabbit = {
	  jumps: true,
	  __proto__: animal
	};
	
	// Object.keys 只返回自己的 key
	alert(Object.keys(rabbit)); // jumps
	
	// for..in 会遍历自己以及继承的键
	for(let prop in rabbit) alert(prop); // jumps,然后是 eats

F.prototype

如果 F.prototype 是一个对象,那么 new 操作符会使用它为新对象设置 [[Prototype]]

  1. JavaScript 从一开始就有了原型继承。这是 JavaScript 编程语言的核心特性之一。
  2. 默认的 F.prototype,构造器属性
    1. 每个函数都有 "prototype" 属性,即使我们没有提供它。
    2. 默认的 "prototype" 是一个只有属性 constructor 的对象,属性 constructor 指向函数自身。
    3. ……JavaScript 自身并不能确保正确的 "constructor" 函数值。

原生的原型

"prototype" 属性在 JavaScript 自身的核心部分中被广泛地应用。所有的内建构造函数都用到了它。

  1. Object.prototype
    1. let obj = {}; alert( obj ); // "[object Object]" ?
    2. obj = {} 和 obj = new Object() 是一个意思,其中 Object 就是一个内建的对象构造函数,其自身的 prototype 指向一个带有 toString 和其他方法的一个巨大的对象。
  2. 其他内建原型
    1. 其他内建对象,像 ArrayDateFunction 及其他,都在 prototype 上挂载了方法。
  3. 基本数据类型
    1. 最复杂的事情发生在字符串、数字和布尔值上。
    2. 值 null 和 undefined 没有对象包装器
  4. 更改原生原型
    1. 重要:
      1. 原型是全局的,所以很容易造成冲突。如果有两个库都添加了 String.prototype.show 方法,那么其中的一个方法将被另一个覆盖。
      2. 在现代编程中,只有一种情况下允许修改原生原型。那就是 polyfilling。
      3. Polyfilling 是一个术语,表示某个方法在 JavaScript 规范中已存在,但是特定的 JavaScript 引擎尚不支持该方法,那么我们可以通过手动实现它,并用以填充内建原型。
  5. 从原型中借用
    1. 那是指我们从一个对象获取一个方法,并将其复制到另一个对象。一些原生原型的方法通常会被借用。

原型方法,没有 proto 的对象

  1. 原型简史
      • 构造函数的 "prototype" 属性自古以来就起作用。这是使用给定原型创建对象的最古老的方式。
      • 之后,在 2012 年,Object.create 出现在标准中。它提供了使用给定原型创建对象的能力。但没有提供 get/set 它的能力。一些浏览器实现了非标准的 __proto__ 访问器,以为开发者提供更多的灵活性。
      • 之后,在 2015 年,Object.setPrototypeOf 和 Object.getPrototypeOf 被加入到标准中,执行与 __proto__ 相同的功能。由于 __proto__ 实际上已经在所有地方都得到了实现,但它已过时,所以被加入到该标准的附件 B 中,即:在非浏览器环境下,它的支持是可选的。
      • 之后,在 2022 年,官方允许在对象字面量 {...} 中使用 __proto__(从附录 B 中移出来了),但不能用作 getter/setter obj.__proto__(仍在附录 B 中)。
    1. 如果速度很重要,就请不要修改已存在的对象的 [[Prototype]]
  2. "Very plain" objects
    1. 对象可以用作关联数组(associative arrays)来存储键/值对。
  3. 设置和访问原型的现代方法有 - - Object.getPrototypeOf(obj) —— 返回对象 obj 的 [[Prototype]](与 __proto__ 的 getter 相同)。
    • Object.setPrototypeOf(obj, proto) —— 将对象 obj 的 [[Prototype]] 设置为 proto(与 __proto__ 的 setter 相同)。
    • 不推荐使用内建的 __proto__ getter/setter 获取/设置原型,它现在在 ECMA 规范的附录 B 中。
    • 我们还介绍了使用 Object.create(null) 或 {__proto__: null} 创建的无原型的对象。