-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
【进阶5-1期】重新认识构造函数、原型和原型链 #32
Comments
辛苦了, 继续等你的系列! |
优质的博文 |
满满的干货,辛苦 |
辛苦,每天都拜读,期待您的更新 |
干得漂亮 |
跟着大佬的步伐走 |
干得漂亮 |
基本类型并不存在原型链,对它们做一些操作,比如toString,存在一个boxing和unboxing的过程 |
图片全部挂了,建议使用github作为图库 |
构造函数什么是构造函数
// 木易杨
function Parent(age) {
this.age = age;
}
var p = new Parent(50);
p.constructor === Parent; // true
p.constructor === Object; // false 构造函数本身就是一个函数,与普通函数没有任何区别,不过为了规范一般将其首字母大写。构造函数和普通函数的区别在于,使用 那是不是意味着普通函数创建的实例没有 // 木易杨
// 普通函数
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 是这样介绍 The
// 木易杨
new Symbol(123); // Symbol is not a constructor
Symbol(123); // Symbol(123) 虽然是基本数据类型,但 // 木易杨
var sym = Symbol(123);
console.log( sym );
// Symbol(123)
console.log( sym.constructor );
// ƒ Symbol() { [native code] } 这里的 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); 对于基本类型来说是只读的,比如 // 木易杨
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) 为什么呢?因为创建他们的是只读的原生构造函数( 模拟实现 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
__proto__
引言
前端进阶系列已经到第 5 期啦,本期正式开始原型
Prototype
系列。本篇文章重点介绍构造函数、原型和原型链相关知识,如果你还不知道
Symbol
是不是构造函数、constructor
属性是否只读、prototype
、[[Prototype]]
和__proto__
的区别、什么是原型链,建议你好好阅读本文,希望对你有所帮助。下图是本文的思维导图,高清思维导图和更多文章请看我的 Github。
构造函数
什么是构造函数
constructor
返回创建实例对象时构造函数的引用。此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。构造函数本身就是一个函数,与普通函数没有任何区别,不过为了规范一般将其首字母大写。构造函数和普通函数的区别在于,使用
new
生成实例的函数就是构造函数,直接调用的就是普通函数。那是不是意味着普通函数创建的实例没有
constructor
属性呢?不一定。Symbol 是构造函数吗
MDN 是这样介绍
Symbol
的Symbol
是基本数据类型,但作为构造函数来说它并不完整,因为它不支持语法new Symbol()
,Chrome 认为其不是构造函数,如果要生成实例直接使用Symbol()
即可。(来自 MDN)虽然是基本数据类型,但
Symbol(123)
实例可以获取constructor
属性值。这里的
constructor
属性来自哪里?其实是Symbol
原型上的,即Symbol.prototype.constructor
返回创建实例原型的函数, 默认为Symbol
函数。constructor 值只读吗
这个得分情况,对于引用类型来说
constructor
属性值是可以修改的,但是对于基本类型来说是只读的。引用类型情况其值可修改这个很好理解,比如原型链继承方案中,就需要对
constructor
重新赋值进行修正。对于基本类型来说是只读的,比如
1、“muyiy”、true、Symbol
,当然null
和undefined
是没有constructor
属性的。为什么呢?因为创建他们的是只读的原生构造函数(
native constructors
),这个例子也说明了依赖一个对象的constructor
属性并不安全。模拟实现 new
说到这里就要聊聊
new
的实现了,实现代码如下。之前写过一篇文章解析
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 proto,最先被 Firefox使用,后来在 ES6 被列为 Javascript 的标准内建属性。[[Prototype]]
是对象的一个内部属性,外部代码无法直接访问。这里用
p.__proto__
获取对象的原型,__proto__
是每个实例上都有的属性,prototype
是构造函数的属性,这两个并不一样,但p.__proto__
和Parent.prototype
指向同一个对象。所以构造函数
Parent
、Parent.prototype
和p
的关系如下图。注意点
__proto__
属性在ES6
时才被标准化,以确保 Web 浏览器的兼容性,但是不推荐使用,除了标准化的原因之外还有性能问题。为了更好的支持,推荐使用Object.getPrototypeOf()
。如果要读取或修改对象的
[[Prototype]]
属性,建议使用如下方案,但是此时设置对象的[[Prototype]]
依旧是一个缓慢的操作,如果性能是一个问题,就要避免这种操作。如果要创建一个新对象,同时继承另一个对象的
[[Prototype]]
,推荐使用Object.create()
。这里
child
是一个新的空对象,有一个指向对象 p 的指针__proto__
。优化实现 new
正如上面介绍的不建议使用
__proto__
,所以我们使用Object.create()
来模拟实现,优化后的代码如下。原型链
每个对象拥有一个原型对象,通过
__proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null
。这种关系被称为原型链 (prototype chain),通过原型链一个对象会拥有定义在其他对象中的属性和方法。我们看下面一个例子
这里
p.constructor
指向Parent
,那是不是意味着p
实例存在constructor
属性呢?并不是。我们打印下
p
值就知道了。由图可以看到实例对象
p
本身没有constructor
属性,是通过原型链向上查找__proto__
,最终查找到constructor
属性,该属性指向Parent
。下图展示了原型链的运作机制。
小结
Symbol
作为构造函数来说并不完整,因为不支持语法new Symbol()
,但其原型上拥有constructor
属性,即Symbol.prototype.constructor
。constructor
属性值是可以修改的,但是对于基本类型来说是只读的,当然null
和undefined
没有constructor
属性。__proto__
是每个实例上都有的属性,prototype
是构造函数的属性,这两个并不一样,但p.__proto__
和Parent.prototype
指向同一个对象。__proto__
属性在ES6
时被标准化,但因为性能问题并不推荐使用,推荐使用Object.getPrototypeOf()
。__proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null
,这就是原型链。参考
进阶系列目录
交流
进阶系列文章汇总如下,内有优质前端资料,觉得不错点个star。
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
The text was updated successfully, but these errors were encountered: