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. 其他内建对象,像 Array、Date、Function 及其他,都在 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} 创建的无原型的对象。