JavaScript 原型以及原型链。
1. 原型
-
隐式原型
__proto__
每个对象都有一个隐式原型__proto__
,对象独有,null
,undefined
没有,并且在js
中,Object.prototype
的__proto__
是null
,因为是最顶端,如下代码:
var arr = [];
var obj = {};
var set = new Set();
var map = new Map();
var sym = Symbol();
var fn = function () {};
var date = new Date();
var reg = /\d+/;
var err = new Error();
var int = new Int8Array();
var float = new Float32Array();
var bool = true;
console.log(arr.__proto__, `arr.__proto__`);
console.log(obj.__proto__, `obj.__proto__`);
console.log(set.__proto__, `set.__proto__`);
console.log(map.__proto__, `map.__proto__`);
console.log(sym.__proto__, `sym.__proto__`);
console.log(fn.__proto__, `fn.__proto__`);
console.log(date.__proto__, `date.__proto__`);
console.log(reg.__proto__, `reg.__proto__`);
console.log(err.__proto__, `err.__proto__`);
console.log(int.__proto__, `int.__proto__`);
console.log(float.__proto__, `float.__proto__`);
console.log(bool.__proto__, `bool.__proto__`);
控制台输出:
当试图打印null
,undefined
的__proto__
时
var nul = null;
console.log(nul.__proto__, `nul.__proto__`);
var unde = undefined;
console.log(unde.__proto__, `unde.__proto__`);
-
显式原型
prototype
prototype
为函数独有,如下代码:
var arr = [];
var obj = {};
var set = new Set();
var map = new Map();
var sym = Symbol();
var fn = function () {};
var date = new Date();
var reg = /\d+/;
var err = new Error();
var int = new Int8Array();
var float = new Float32Array();
var bool = true;
console.log(arr.prototype, `arr.prototype`);
console.log(obj.prototype, `obj.prototype`);
console.log(set.prototype, `set.prototype`);
console.log(map.prototype, `map.prototype`);
console.log(sym.prototype, `sym.prototype`);
console.log(fn.prototype, `fn.prototype`);
console.log(date.prototype, `date.prototype`);
console.log(reg.prototype, `reg.prototype`);
console.log(err.prototype, `err.prototype`);
console.log(int.prototype, `int.prototype`);
console.log(float.prototype, `float.prototype`);
console.log(bool.prototype, `bool.prototype`);
控制台输出:
可以看出只有fn
拥有prototype
;
-
隐式原型指向
隐式原型指向__proto__
指向是根据它的构造函数的原型对象(prototype
),比如var obj = {}; obj.__proto__
指向的就是Object.prototype
,只有为{}
时,才是直接指向Object.prototype
,例如var arr = []; arr.__proto__
先指向的是Array.prototype
,然后才是Object.prototype
;如下代码:
var arr = [];
var obj = {};
var set = new Set();
var map = new Map();
var sym = Symbol();
var fn = function () {};
var date = new Date();
var reg = /\d+/;
var err = new Error();
var int = new Int8Array();
var float = new Float32Array();
var bool = true;
console.log(arr.__proto__ === Array.prototype, `arr.__proto__ === Array.prototype`);
console.log(obj.__proto__ === Object.prototype, `obj.__proto__ === Object.prototype`);
console.log(set.__proto__ === Set.prototype, `set.__proto__ === Set.prototype`);
console.log(map.__proto__ === Map.prototype, `map.__proto__ === Map.prototype`);
console.log(sym.__proto__ === Symbol.prototype, `sym.__proto__ === Symbol.prototype`);
console.log(fn.__proto__ === Function.prototype, `fn.__proto__ === Function.prototype`);
console.log(date.__proto__ === Date.prototype, `date.__proto__ === Date.prototype`);
console.log(reg.__proto__ === RegExp.prototype, `reg.__proto__ === RegExp.prototype`);
console.log(err.__proto__ === Error.prototype, `err.__proto__ === Error.prototype`);
console.log(int.__proto__ === Int8Array.prototype, `int.__proto__ === Int8Array.prototype`);
console.log(float.__proto__ === Float32Array.prototype, `float.__proto__ === Float32Array.prototype`);
console.log(bool.__proto__ === Boolean.prototype, `bool.__proto__ === Boolean.prototype`);
控制台输出
es5新增的class的指向。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName() {
console.log(this.name);
}
}
const p = new Person();
console.log(p.__proto__ === Person.prototype, `p`);
通过构造函数new出来的实例的__proto__
指向的它被创建出来的类的prototype
-
原型指向解析
// 定义一个对象
var obj = {};
// obj 这个对象继承了 Object.prototype 上面的所有属性
// obj 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 obj 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// obj ---> Object.prototype ---> null
obj.__proto__.a = 1;
console.log(obj.a, `obj.a`);
// 1
console.log(Object.prototype.a, `Object.prototype.a`);
// 1
console.log(obj.__proto__ === Object.prototype, `obj.constructor.a`);
// true
控制台输出
这时候打印 objconsole.log(obj);
,如下图:
可以看到[[Prototype]]:Object
,说明obj
的__proto__
指向的是Object.prototype
, 如果打印一个数组呢?var arr = []; console.log(arr)
,如下图:
可以看到[[Prototype]]:Array
,说明arr
的__proto__
指向的是Array.prototype
,那么为什么说arr
先是指向Array.protype
,在指向Object.prototype
呢,展开[[Prototype]]:Array
后,如下图
可以看到最后指向的仍然是Object.prototype
-
原型属性查找流程
首先定义一个类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName() {
console.log(this.name);
}
}
通过new
实例化
const p = new Person();
这时候p
会有两个属性,name
,age
,通过访问p.name
获取数据。如果访问p.foo
则会返回undefined
,这时候在p
身上定义一个属性p.foo = 'foo'
,
p.foo = 'foo';
console.log(p.foo, `p`);
// foo
然后在访问p.foo
,则返回foo
;
从以上操作可以看出,在获取属性时,会首先从p
身上查找属性foo
,如果查找到则返回。然后删除p.foo
属性,在Person
类的prototype
定义一个属性,Person.prototype.foo = 'foo'
;
const p = new Person('test', 15);
Person.prototype.foo = 'foo';
console.log(p.foo, `p`);
// foo
然后在访问p.foo
,仍然可以返回foo
;说明在查找属性时,如果自身p
没有,则回去Person
类的原型查找,查找到则返回,反之返回undefined
另外在访问属性时,js
使用的方法是Object.getPrototypeOf
,所以p.foo === Object.getPrototypeOf(p).foo
,如果Object.getPrototypeOf(p).foo
仍旧没有,它会继续查找 Object.getPrototypeOf(Object.getPrototypeOf(p)).foo