深入解析原型Prototype

2023-05-29 0 236

深入细致导出缺省、蓝本和蓝本链

缺省

甚么是缺省

constructor 回到建立示例第一类时缺省的提及。此特性的值是对表达式这类的提及,而并非两个包涵表达式中文名称的数组。

function Parent(age) { this.age = age; } var p = new Parent(50); p.constructor === Parent; // true p.constructor === Object; // false

缺省这类是两个表达式,与通常表达式没任何人差别,但是为的是规范化通常将其第两个字母小写。缺省和通常表达式的差别是,采用 new 聚合示例的表达式是缺省,间接初始化的是通常表达式。

那呢意味著通常表达式建立的示例没 constructor 特性呢?不一定。

// 通常表达式 function parent2(age) { this.age = age; } var p2 = parent2(50); // undefined // 通常表达式 function parent3(age) { return { age: age } } var p3 = parent3(50); p3.constructor === Object; // true

Symbol 是缺省吗

Symbol 是基本数据类型,但作为缺省来说它并不完整,因为它不支持语法 new Symbol(),Chrome 认为其并非缺省,如果要聚合示例间接采用 Symbol() 即可。(来自MDN

new Symbol(123); // Symbol is not a constructor Symbol(123); // Symbol(123)

虽然是基本数据类型,但 Symbol(123)

var sym = Symbol(123); console.log( sym ); // Symbol(123) console.log( sym.constructor ); // ƒ Symbol() { [native code] }

这里的 constructor 特性来自哪里?其实是 Symbol 蓝本上的,即 Symbol.prototype.constructor 回到建立示例蓝本的表达式, 默认为 Symbol 表达式。

constructor 值只读吗

这个得分情况,对于提及类型来说 constructor 特性值是可以修改的,但是对于基本类型来说是只读的。

提及类型情况其值可修改这个很好理解,比如蓝本链继承方案中,就需要对 constructor重新赋值进行修正。

function Foo() { this.value = 42; } Foo.prototype = { method: function() {} }; function Bar() {} // 设置 Bar 的 prototype 特性为 Foo 的示例第一类 Bar.prototype = new Foo(); Bar.prototype.foo = Hello World; Bar.prototype.constructor === Object; // true // 修正 Bar.prototype.constructor 为 Bar 这类Bar.prototype.constructor = Bar; var test = new Bar() // 建立 Bar 的两个新示例 console.log(test);

对于基本类型来说是只读的,比如 1、“thomas”、true、Symbol,当然 null 和 undefined 是没 constructor 特性的。

function Type() { }; var types = [1, “thomas”, true, Symbol(123)]; for(var i = 0; i < types.length; i++) { types[i].constructor = Type; types[i] = [ types[i].constructor, types[i] instanceof Type, types[i].toString() ]; }; console.log( types.join(“\n”) ); // function Number() { [native code] }, false, 1// function String() { [native code] }, false, thomas // function Boolean() { [native code] }, false, true// function Symbol() { [native code] }, false, Symbol(123)

为甚么呢?因为建立他们的是只读的原生缺省(native constructors),这个例子也说明了依赖两个第一类的 constructor 特性并不安全。

模拟实现 new

说到这里就要聊聊 new 的实现了,实现代码如下。

function create() { // 1、建立两个空的第一类 var obj = new Object(), // 2、获得缺省,同时删除 arguments 中第两个参数 Con = [].shift.call(arguments); // 3、链接到蓝本,obj 可以访问缺省蓝本中的特性 Object.setPrototypeOf(obj, Con.prototype); // 4、绑定 this 实现继承,obj 可以访问到缺省中的特性 var ret = Con.apply(obj, arguments); // 5、优先回到缺省回到的第一类 return ret instanceof Object ? ret : obj; };

蓝本

prototype

JavaScript 是一种基于蓝本的语言 (prototype-based language),这个和 Java 等基于类的语言不一样。

每个第一类拥有两个蓝本第一类,第一类以其蓝本为模板,从蓝本继承方法和特性,这些特性和方法定义在第一类的构造器表达式的 prototype 特性上,而非第一类示例这类。

深入解析原型Prototype

从上面这张图可以发现,Parent 第一类有两个蓝本第一类 Parent.prototype,其上有两个特性,分别是 constructor 和 __proto__,其中 __proto__ 已被弃用。

缺省 Parent 有两个指向蓝本的指针,蓝本 Parent.prototype 有两个指向缺省的指针 Parent.prototype.constructor,如上图所示,其实是两个循环提及。

深入解析原型Prototype

__proto__

上图可以看到 Parent 蓝本( Parent.prototype )上有 __proto__ 特性,这是两个访问器特性(即 getter 表达式和 setter 表达式),通过它可以访问到第一类的内部 [[Prototype]] (两个第一类或 null )。

__proto__ 发音 dunder proto,最先被 Firefox采用,后来在 ES6 被列为 Javascript 的标准内建特性。

[[Prototype]] 是第一类的两个内部特性,外部代码无法间接访问。

遵循 ECMAScript 标准,someObject.[[Prototype]] 符号用于指向 someObject 的蓝本。

深入解析原型Prototype

proto__ 是每个示例上都有的特性,prototype 是缺省的特性,这两个并不一样,但 p.__proto__ 和 Parent.prototype 指向同两个第一类。

// 木易杨 function Parent() {} var p = new Parent(); p.__proto__ === Parent.prototype // true

所以缺省 Parent、Parent.prototype 和 p 的关系如下图。

深入解析原型Prototype

注意点

__proto__ 特性在 ES6 时才被标准化,以确保 Web 浏览器的兼容性,但是不推荐采用,除了标准化的原因之外还有性能问题。为的是更好的支持,推荐采用 Object.getPrototypeOf()。

通过改变两个第一类的 [[Prototype]] 特性来改变和继承特性会对性能造成非常严重的影响,并且性能消耗的时间也并非简单的花费在 obj.__proto__ = … 语句上, 它还会影响到所有继承自该 [[Prototype]] 的第一类,如果你关心性能,你就不应该修改两个第一类的 [[Prototype]]。

如果要读取或修改第一类的 [[Prototype]] 特性,建议采用如下方案,但是此时设置第一类的 [[Prototype]] 依旧是两个缓慢的操作,如果性能是两个问题,就要避免这种操作。

Object.getPrototypeOf() Reflect.getPrototypeOf() // 修改 Object.setPrototypeOf() Reflect.setPrototypeOf()

如果要建立两个新第一类,同时继承另两个第一类的 [[Prototype]] ,推荐采用 Object.create()。

function Parent() { age: 50 }; var p = new Parent(); var child = Object.create(p);

这里 child 是两个新的空第一类,有两个指向第一类 p 的指针 __proto__。

优化实现 new

正如上面介绍的不建议采用 __proto__,所以我们采用 Object.create() 来模拟实现,优化后的代码如下。

function create() { // 1、获得缺省,同时删除 arguments 中第两个参数 Con = [].shift.call(arguments); // 2、建立两个空的第一类并链接到蓝本,obj 可以访问缺省蓝本中的特性 var obj = Object.create(Con.prototype); // 3、绑定 this 实现继承,obj 可以访问到缺省中的特性 var ret = Con.apply(obj, arguments); // 4、优先回到缺省回到的第一类 return ret instanceof Object ? ret : obj; };

蓝本链

每个第一类拥有两个蓝本第一类,通过 __proto__ 指针指向上两个蓝本 ,并从中继承方法和特性,同时蓝本第一类也可能拥有蓝本,这样一层一层,最终指向 null。这种关系被称为蓝本链 (prototype chain),通过蓝本链两个第一类会拥有定义在其他第一类中的特性和方法。

我们看下面两个例子

function Parent(age) { this.age = age; } var p = new Parent(50); p.constructor === Parent; // true

这里 p.constructor 指向 Parent,那呢意味著 p 示例存在 constructor 特性呢?并并非。

我们打印下 p 值就知道了。

深入解析原型Prototype

由图可以看到示例第一类 p 这类没 constructor 特性,是通过蓝本链向上查找 __proto__ ,最终查找到 constructor 特性,该特性指向 Parent。

function Parent(age) { this.age = age; } var p = new Parent(50); p; // Parent {age: 50} p.__proto__ === Parent.prototype; // true p.__proto__.__proto__ === Object.prototype; // true p.__proto__.__proto__.__proto__ === null; // true

下图展示了蓝本链的运作机制。

深入解析原型Prototype

小结

Symbol 作为缺省来说并不完整,因为不支持语法 new Symbol(),但其蓝本上拥有 constructor 特性,即 Symbol.prototype.constructor。提及类型 constructor 特性值是可以修改的,但是对于基本类型来说是只读的,当然 null 和 undefined 没 constructor 特性。__proto__ 是每个示例上都有的特性,prototype 是缺省的特性,在示例上并不存在,所以这两个并不一样,但 p.__proto__ 和 Parent.prototype 指向同两个第一类。__proto__ 特性在 ES6 时被标准化,但因为性能问题并不推荐采用,推荐采用 Object.getPrototypeOf()。每个第一类拥有两个蓝本第一类,通过 __proto__ 指针指向上两个蓝本 ,并从中继承方法和特性,同时蓝本第一类也可能拥有蓝本,这样一层一层,最终指向 null,这是蓝本链。

参考链接:https://muyiy.cn/blog/5/5.1.html#%E5%8E%9F%E5%9E%8B%E9%93%BE

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务