原型(prototype) 和 原型链(__proto__) —— 别再为 JavaScript 的原型苦恼了

简介: 原型(prototype) 和 原型链(__proto__) —— 别再为 JavaScript 的原型苦恼了

image.png

一、原型(prototype)

理解原型

  • 在 JavaScript 中只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。
  • 在自定义构造函数时,原型对象默认只会获得 constructor 属性,其他的所有方法都继承自Object。每次调用构造函数创建一个新实例,这个实例的内部[[Prototype]]指针就会被赋值为构造函数的原型对象。
  • JS 脚本中没有直接访问 [[Prototype]] 特性的标准方式,但 Firefox、Safari 和 Chrome 会在每个对象上暴露 __proto__ 属性,通过这个属性可以访问对象的原型

普通对象、函数对象和原型

  • JavaScript 中一切皆是对象,但是针对原型而言,对象还得区分为【函数对象】和 【普通对象】,可以认为是【函数对象】是【普通对象】中的一种特例。
  • 通过对原型的理解,可以知道当对象被创建时,浏览器会给每个对象上添加一个 __proto__ 属性,这个属性更像一个指针,它指向的是创建这个对象的构造函数的原型。
  • 而函数对象也属于对象,于是在函数被创建时,也会拥有这个 __proto__ 属性,特殊的是函数还可以拥有一个 prototype 属性,也就是指当前这个函数的原型。
var obj = {};
console.log(obj.__proto__);// {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(obj.prototype);// undefined
function add(){}
console.log(add.__proto__);// ƒ () { [native code] }
console.log(add.prototype);// {constructor: ƒ}
复制代码

【原型】、【构造函数】以及【实例对象】的关系

实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有

  • 每个函数都会创建一个 prototype 属性,这个属性是一个对象,也就是当前函数的原型.
  • prototype 对象上存在一个 constructor 属性,指向这个函数本身.
function person(){}
console.log(person.prototype); // {constructor: ƒ}
console.log(person.prototype.constructor === person); // true
复制代码
  • 实例对象是对构造函数进行 new 操作后,在构造函数中返回的对象. 实例的 __proto__ 属性指向实例构造函数的 prototype
// 1. 显式返回对象
function Person() {
  return {
    name: 'zs'
  }
}
let p = new Person; // 实例对象
console.log(p); // {name: 'zs', age: 18}
// 2. 取默认返回的对象
function Person() {
  this.age = 18;
  this.name = 'zs';
}
let p = new Person; // 实例对象
console.log(p); // Person {age: 18, name: 'zs'}
console.log(p.__proto__ === Person.prototype); // true
复制代码
  • 原型上的属性和方法对特定引用类型的实例是共享的,当实例上的属性和原型上属性存在同名时,会优先使用实例上的属性对应的内容.
function Person() {
  this.age = 18;
  this.name = 'zs';
  this.otherName = 'otherName in instance';
}
Person.prototype.otherName = 'otherName of prototype';
Person.prototype.add = function (a, b) {
  return a + b;
}
let p = new Person; // 实例对象
console.log(p); // Person {age: 18, name: 'zs'}
console.log(p.add(1, 2)); // 3
console.log(p.otherName); // otherName in instance
复制代码

原型的搜索机制

在读取实例上的属性时,首先会在实例上搜索这个属性。如果找到了,就停止搜索,直接使用当前的搜索结果。如果没找到,则会继承搜索实例的原型。

二、原型链

理解原型链

重温一下构造函数、原型和实例的关系:每个构造函数上都有一个原型对象(prototype),原型上有一个属性(constructor) 指回构造函数,而实例上有一个内部指针(__proto__) 指向构造函数的原型。

如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和一个或多个原型之间构造了一条原型链。

原型链 和 继承

  • ECMA-262 把原型链定义为 ECMAScript 的主要继承方式,ES5 和 ES6 都有基于原型链实现继承的方式,但本质上都基于 ES5 中的继承方式
  • 【原型】上的所有属性和方法都能被【原型指向的构造函数所实例化的对象】所共享,而原型链就是通过原型继承多个引用类型的属性和方法
  • 原型链实现继承之后,搜索就可以继承向上,搜索原型的原型,搜索范围更广
  • 原型与实例的关系可以通过两种方式来确定:
  • 方式一 是使用 instanceof 操作符,如果一个实例的原型链中出现过相应的构造函数,则 instanceof 返回 true
  • 方式二 是使用 isPrototypeOf()方法,原型链中的每个原型都可以调用这个方法,如:Object.prototype.isPrototypeOf(instance)

原型链图解

说实话,个人不是很建议看这种图解,因为在你不是很清楚各种原型指向的情况下,只会越看越乱。所以下面给出几个关键点:

  • 默认情况下,所有引用类型都【继承】自 Object,这是通过原型链实现的,这也是为什么自定义类型能够继承包括 toString()、valueOf()在内的所有默认方法的原因
  • 任何函数的默认原型(prototype) 都是一个 Object 的实例,这意味着这个实例有一个内部指针(__proto__) 指向 Object.prototype
  • 正常情况下,任何类型实例都有一个指针(__proto__) 都指向这个实例的构造函数(constructor) 的原型(prototype)
  • 原型是基于普通函数或构造函数存在的,正常情况下,对于基本数据类型而言,如果它没有构造函数,那么它就没有所谓的原型,比如:null 和 undefined
  • 正常情况下,所有函数的构造函数都是 Function,意味着所有函数的 __proto__ 属性指向的都是 Function.prototype
var str = '';
var num = 1;
var bool = true;
var sym = Symbol("sym");
var obj = {};
var arr = [];
var func = function (){}
console.log(str.constructor); //  ƒ String() { [native code] }
console.log(num.constructor); //  ƒ Number() { [native code] }
console.log(bool.constructor); // ƒ Boolean() { [native code] } 
console.log(sym.constructor); //  ƒ Symbol() { [native code] }
console.log(obj.constructor); //  ƒ Object() { [native code] }
console.log(arr.constructor); //  ƒ Array() { [native code] }
console.log(func.constructor); // ƒ Function() { [native code] }
console.log(str.__proto__ === String.prototype); // true
console.log(num.__proto__ === Number.prototype); // true
console.log(bool.__proto__ === Boolean.prototype); // true
console.log(sym.__proto__ === Symbol.prototype); // true
console.log(obj.__proto__ === Object.prototype); // true
console.log(arr.__proto__ === Array.prototype); // true
console.log(func.__proto__ === Function.prototype); // true
// 任何类型的原型的原型都是 Object 的实例
console.log([any].prototype.__proto__ === Object.prototype); // true
// 任何函数的 __proto__ 都指向 Function.prototype
console.log(String.__proto__ === Function.prototype); // true
console.log(Number.__proto__ === Function.prototype); // true
console.log(Boolean.__proto__ === Function.prototype); // true
console.log(Symbol.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Array.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
复制代码


image.png

目录
相关文章
|
1月前
|
JavaScript 前端开发 开发者
理解JavaScript中的原型链:基础与实践
【10月更文挑战第8天】理解JavaScript中的原型链:基础与实践
|
16天前
|
JavaScript 前端开发
JavaScript 原型链的实现原理是什么?
JavaScript 原型链的实现原理是通过构造函数的`prototype`属性、对象的`__proto__`属性以及属性查找机制等相互配合,构建了一个从对象到`Object.prototype`的链式结构,实现了对象之间的继承、属性共享和动态扩展等功能,为 JavaScript 的面向对象编程提供了强大的支持。
|
16天前
|
JavaScript 前端开发
原型链在 JavaScript 中的作用是什么?
原型链是 JavaScript 中实现面向对象编程的重要机制之一,它为代码的组织、复用、扩展和多态性提供了强大的支持,使得 JavaScript 能够以简洁而灵活的方式构建复杂的应用程序。深入理解和熟练运用原型链,对于提升 JavaScript 编程能力和开发高质量的应用具有重要意义。
|
17天前
|
JavaScript 前端开发
如何使用原型链继承实现 JavaScript 继承?
【10月更文挑战第22天】使用原型链继承可以实现JavaScript中的继承关系,但需要注意其共享性、查找效率以及参数传递等问题,根据具体的应用场景合理地选择和使用继承方式,以满足代码的复用性和可维护性要求。
|
28天前
|
JavaScript 前端开发 开发者
探索JavaScript原型链:深入理解与实战应用
【10月更文挑战第21天】探索JavaScript原型链:深入理解与实战应用
29 1
|
1月前
|
JavaScript 前端开发 开发者
深入理解JavaScript原型链:从基础到进阶
【10月更文挑战第13天】深入理解JavaScript原型链:从基础到进阶
25 0
|
1月前
|
JavaScript 前端开发 开发者
原型链深入解析:JavaScript中的核心机制
【10月更文挑战第13天】原型链深入解析:JavaScript中的核心机制
32 0
|
1月前
|
JavaScript 前端开发 安全
深入理解JavaScript原型链:从基础到进阶
【10月更文挑战第13天】深入理解JavaScript原型链:从基础到进阶
28 0
|
2月前
|
JSON JavaScript 前端开发
js原型继承|26
js原型继承|26
|
1月前
|
JavaScript 前端开发
JavaScript - 测试 Prototype
JavaScript - 测试 Prototype
12 0