javascript中的new原理及实现-CSDN博客
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
在js中我们通过new运算符来创建一个对象它是一个高频的操作。我们一般只是去用它而很少关注它是如何实现的它的工作机制是什么。
1 简介
本文介绍new的功能用法补充介绍了不加new也同样创建对象的方式分析了new的原理最后模拟了new的实现。
学习本文内容需要你了解js中对象原型链,call,bind,arguments的用法。
2.new 的基本用法
我们通过new来创建对象它的基本格式是
var 对象 = new 函数([参数])
这里的函数可以是内置构造器也可以是用户自己定义的函数。
例如
var arr = new Array ();
此时arr将可以使用Array.prototype上的全部方法。
更一般的情况我们会使用自己定义的构造器。
function F(name,age){
this.name = name;
this.age = age
}
F.prototype.hello = function(){
console.log(this.name,this.age)
}
var f1 = new F('curry', 30);
console.log(f1)
f1.hello()
对如上的代码有几点说明如下
函数F
在被调用的过程中在前面加 new 所以这个函数是被当作
构造器来使用了。
f1
之所以可以调用 hello方法也是因为原型链的缘故。
3 构造器的返回值
一般来讲如果你要把一个函数当做构造函数来使用在这个函数的内部是不应该
去设置返回值的。但是如果它设置了返回值呢
先说出答案如下
return后面跟着不是对象就会不管return语句返回this对象
return后面跟着一个对象new会返回return语句指定的对象
先来看构造器中return后面跟着不是对象的情况。
var Vehicle = function () {
this.price = 1000;
return 1000;
};
(new Vehicle()) === 1000// false
上面代码中构造函数Vehicle的return语句返回一个数值。这时new命令就会忽略这个return语句就当它不存在还是正常返回“构造”后的this对象。
但是如果return语句返回的是一个对象new命令会返回这个新对象而不再是this对象这一点需要特别引起注意。
var Vehicle = function (){
this.price = 1000;
return { price: 2000 };
};
(new Vehicle()).price
// 2000
上面代码中构造函数Vehicle的return语句返回的是一个新对象。new命令会返回这个对象而不是this对象。
4 不加new也能创建对象吗
对上面的代码我们稍微改一下,在使用构造器时故意去掉new这个关键字。如下
function F(name,age){
this.name = name;
this.age = age
}
var f1 = F('curry', 30); // 不加new
console.log(f1)
此时我们把F当作一个普通的函数来调用由于在函数F内部并没有明确写出return语句所以f1的值是undefined。同时上面的代码还会有另一个隐藏的后果在执行F时由于this的值是指向window所以上面的代码还会给window对象添加两个属性。如下
那么问题来了如何确保这个F
只能被用作构造器而不能当作普通函数来用呢
两种解决思路
如果不加new就报错。
如果不加new就偷着给你加上。
4.1 构造函数内部使用严格模式
为了保证构造函数必须与new命令一起使用一个解决办法是构造函数内部使用严格模式即第一行加上use strict。这样的话一旦忘了使用new命令直接调用构造函数就会报错。
function F(name,age){
'use strict'; // 这句新加的
this.name = name;
this.age = age
}
var f1 = F('curry', 30); // 不加new
console.log(f1)
上面的代码会报错错误是Uncaught TypeError: Cannot set property 'name' of undefined
。因为在函数内部开启了严格模式之后函数内部的this将不会默认指向window
,它的值会是undefined。
一旦代码报错了相当于提醒你必须给加上new你就自己给它加上吧。
4.2 自动加上new
还可以在构造函数内部判断当前调用是否使用new命令如果发现没有使用new则直接返回一个实例对象。
function F(name,age){
// 如果没有用newthis就不会是F的实例
if (!(this instanceof F)) {
return new F(name,age);
}
this.name = name;
this.age = age
}
var f1 = new F('a','30');
var f2 = F('b','30');
console.log(f2)
上面代码中的构造函数不管加不加new命令都会得到同样的结果。如下
5 new 原理
使用new命令时在构造函数内部依次执行下面的步骤。
第一步创建一个空对象作为将要返回的对象。
第二步将这个空对象的原型指向构造函数的prototype属性。这一步的作用是让这个对象能沿着原型链去使用构造函数中prototype上的方法。
第三步将这个空对象赋值给构造函数内部的this关键字执行构造函数。这一步的作用是让构造器中设置在this上的属性最终设置在这个对象上。
第四步返回这个对象。
以如下代码为例
function F(name,age){
this.name = name;
this.age = age
}
F.prototype.hello = function(){
console.log(this.name,this.age)
}
var f = new F('a','30');
则上面四步的伪代码如下
第一步var obj = {}
第二步obj.__proto__ = F.prototype
第三步F.apply(obj,参数)
第四步return obj
下面模拟一下new的实现。由于new是一个关键字我们写一个单独的函数_new来模拟最终的目标是
function F(name,age){
this.name = name;
this.age = age
}
F.prototype.hello = function(){
console.log(this.name,this.age)
}
// 使用模拟new,
var f1 = _new(F,'a',30);
// 希望达到与new F('a',30)一致的效果
f1.hello();
你可以先想一想 如何实现_new哈。
下面是一个参考实现
function _new(constructor, ...args) {
// 创建一个新对象将其原型设置为构造函数的原型
const newObj = Object.create(constructor.prototype);
// 调用构造函数并将新对象作为上下文
const result = constructor.apply(newObj, args);
// 如果构造函数有显式返回一个对象则返回该对象否则返回新对象
return typeof result === 'object' && result !== null ? result : newObj;
}
// 测试
function F(name, age) {
this.name = name;
this.age = age;
}
F.prototype.hello = function() {
console.log(this.name, this.age);
}
var f1 = _new(F, 'a', 30);
f1.hello(); // 输出a 30
代码解释
// 创建一个新对象将其原型设置为构造函数的原型
const newObj = Object.create(constructor.prototype);
// 等同于
const newObj = {}; // 创建一个空对象
newObj.__proto__ = constructor.prototype; // 将新对象的原型设置为构造函数的原型
// 调用构造函数并将新对象作为上下文
const result = constructor.apply(newObj, args);
解释
当我们调用 `constructor.apply(newObj, args)` 时我们将 `constructor` 构造函数作为函数调用并将 `newObj` 对象作为其上下文。
关于 `apply` 方法它是 JavaScript 中用于调用函数的方法可以设置函数调用时的上下文对象以及参数列表。它接收两个参数上下文对象和参数数组。
在这行代码中我们将 `newObj` 对象作为上下文对象这样在构造函数内部可以通过 `this` 来引用这个新创建的对象。然后我们将 `args` 数组展开作为参数列表传递给构造函数。
这样构造函数就会在 `newObj` 对象的上下文中执行从而将构造函数内部的属性和方法赋值给 `newObj` 对象。
最后我们的实现返回了这个新创建的对象以便我们可以像使用 `new` 关键字一样访问该对象的属性和方法。
版权信息凡人进阶。