一、原型(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 复制代码