【进阶5-1期】重新认识构造函数、原型和原型链
栏目: JavaScript · 发布时间: 5年前
内容简介:前端进阶系列已经到第 5 期啦,本期正式开始原型本篇文章重点介绍构造函数、原型和原型链相关知识,如果你还不知道下图是本文的思维导图,高清思维导图和更多文章请看我的
前端进阶系列已经到第 5 期啦,本期正式开始原型 Prototype
系列。
本篇文章重点介绍构造函数、原型和原型链相关知识,如果你还不知道 Symbol
是不是构造函数、 constructor
属性是否只读、 prototype
、 [[Prototype]]
和 __proto__
的区别、什么是原型链,建议你好好阅读本文,希望对你有所帮助。
下图是本文的思维导图,高清思维导图和更多文章请看我的 Github 。
构造函数
什么是构造函数
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 是构造函数吗
MDN 是这样介绍 Symbol
的
The Symbol()
function returns a value of type symbol , has static properties that expose several members of built-in objects, has static methods that expose the global symbol registry, and resembles a built-in object class but is incomplete as a constructor because it does not support the syntax " new Symbol()
".
Symbol
是基本数据类型,但作为构造函数来说它并不完整,因为它不支持语法 new Symbol()
,Chrome 认为其不是构造函数,如果要生成实例直接使用 Symbol()
即可。(来自MDN)
// 木易杨 new Symbol(123); // Symbol is not a constructor Symbol(123); // Symbol(123) 复制代码
虽然是基本数据类型,但 Symbol(123)
实例可以获取 constructor
属性值。
// 木易杨 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、“muyiy”、true、Symbol
,当然 null
和 undefined
是没有 constructor
属性的。
// 木易杨 function Type() { }; var types = [1, "muyiy", 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, muyiy // 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; }; 复制代码
之前写过一篇文章解析 new
的模拟实现过程,如果你对实现过程还不了解的话点击阅读。「 【进阶3-5期】深度解析 new 原理及模拟实现 」
原型
prototype
JavaScript
是一种 基于原型的语言 (prototype-based language),这个和 Java
等基于类的语言不一样。
每个对象拥有一个 原型对象 ,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype
属性上,而非对象实例本身。
从上面这张图可以发现, Parent
对象有一个原型对象 Parent.prototype
,其上有两个属性,分别是 constructor
和 __proto__
,其中 __proto__
已被弃用。
构造函数 Parent
有一个指向原型的指针,原型 Parent.prototype
有一个指向构造函数的指针 Parent.prototype.constructor
,如上图所示,其实就是一个循环引用。
__proto__
上图可以看到 Parent 原型( Parent.prototype
)上有 __proto__
属性,这是一个 访问器属性 (即 getter 函数和 setter 函数),通过它可以访问到对象的内部 [[Prototype]]
(一个对象或 null
)。
__proto__
发音 dunder prototype,最先被 Firefox使用,后来在 ES6 被列为 Javascript 的标准内建属性。
[[Prototype]]
是对象的一个内部属性,外部代码无法直接访问。
遵循 ECMAScript 标准,someObject.[[Prototype]] 符号用于指向 someObject 的原型。
这里用 p.__proto__
获取对象的原型, __proto__
是每个实例上都有的属性, prototype
是构造函数的属性,这两个并不一样,但 p.__proto__
和 Parent.prototype
指向同一个对象。
// 木易杨 function Parent() {} var p = new Parent(); p.__proto__ === Parent.prototype // true 复制代码
所以构造函数 Parent
、 Parent.prototype
和 p
的关系如下图。
注意点
__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 复制代码
这里 parent.constructor
指向 Parent
,那是不是意味着 parent
实例存在 constructor
属性呢?并不是。
我们打印下 parent
值就知道了。
由图可以看到实例对象 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 复制代码
下图展示了原型链的运作机制。
小结
-
Symbol
作为构造函数来说并不完整,因为不支持语法new Symbol()
,但其原型上拥有constructor
属性,即Symbol.prototype.constructor
。 - 引用类型
constructor
属性值是可以修改的,但是对于基本类型来说是只读的,当然null
和undefined
没有constructor
属性。 -
__proto__
是每个实例上都有的属性,prototype
是构造函数的属性,这两个并不一样,但p.__proto__
和Parent.prototype
指向同一个对象。 -
__proto__
属性在ES6
时被标准化,但因为性能问题并不推荐使用,推荐使用Object.getPrototypeOf()
。 - 每个对象拥有一个原型对象,通过
__proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null
,这就是原型链。
参考
进阶系列目录
- 【进阶1期】 调用堆栈
- 【进阶2期】 作用域闭包
- 【进阶3期】 this全面解析
- 【进阶4期】 深浅拷贝原理
- 【进阶5期】 原型Prototype
- 【进阶6期】 高阶函数
- 【进阶7期】 事件机制
- 【进阶8期】 Event Loop原理
- 【进阶9期】 Promise原理
- 【进阶10期】Async/Await原理
- 【进阶11期】防抖/节流原理
- 【进阶12期】模块化详解
- 【进阶13期】ES6重难点
- 【进阶14期】计算机网络概述
- 【进阶15期】浏览器渲染原理
- 【进阶16期】webpack配置
- 【进阶17期】webpack原理
- 【进阶18期】前端监控
- 【进阶19期】跨域和安全
- 【进阶20期】性能优化
- 【进阶21期】VirtualDom原理
- 【进阶22期】Diff算法
- 【进阶23期】MVVM双向绑定
- 【进阶24期】Vuex原理
- 【进阶25期】Redux原理
- 【进阶26期】路由原理
- 【进阶27期】VueRouter源码解析
- 【进阶28期】ReactRouter源码解析
交流
进阶系列文章汇总如下,内有优质前端资料,觉得不错点个star。
我是木易杨,网易高级前端工程师,跟着我 每周重点攻克一个前端面试重难点 。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
以上所述就是小编给大家介绍的《【进阶5-1期】重新认识构造函数、原型和原型链》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 构造函数、原型、原型链、继承
- 详解js原型,构造函数以及class之间的原型关系
- JavaScript原型与构造函数笔记
- 构造函数的继承——使用原型对象(prototype)
- 构造函数(constructor)与原型链(prototype)关系
- 《JavaScript面向对象精要》之四:构造函数和原型对象
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Trading and Exchanges
Larry Harris / Oxford University Press, USA / 2002-10-24 / USD 95.00
This book is about trading, the people who trade securities and contracts, the marketplaces where they trade, and the rules that govern it. Readers will learn about investors, brokers, dealers, arbit......一起来看看 《Trading and Exchanges》 这本书的介绍吧!