首先我们看下new一个构造函数时具体执行了什么操作,假设这个构造函数名为Person。
- 在内存中新建一个空对象。这里用p代替这个对象。
- 将这个空对象的原型
__proto__
,指向构造函数的prototype
属性。即空对象继承了构造函数的原型。
p.__proto__ = Person.prototype
- 将这个空对象赋值给函数内部的this关键字,即this变量指向这个空对象。
Person.call(p)
//解释上述this指向的问题 - 开始执行构造函数内部的代码。如果有传参,则根据定义的键值和传入的参数,依次给这个空对象添加上键值对。
- 在构造函数语句末尾隐式
return
这个构造后的对象p。
我的理解new命令的内部过程
// 手写new函数
// constructFunction即是构造函数,如上述的Person
function newFun (/* 构造函数 */ constructFunction, /* 构造函数参数 */ params){
let obj = {};
obj.__proto__ = constructFunction.prototype;
//arguments是new调用时传的参数,用call需要把数组展开
constructFunction.call(obj, ...arguments);
return function(){
return obj;
}
}
这个是阮一峰博客写的new命令简化内部流程
function _new(/* 构造函数 */ constructFunction, /* 构造函数参数 */ params) {
// 将 arguments 对象转为数组
var args = [].slice.call(arguments);
// 取出构造函数
var constructor = args.shift();
// 创建一个空对象,继承构造函数的 prototype 属性
var context = Object.create(constructor.prototype);
// 执行构造函数
var result = constructor.apply(context, args);
// 如果返回结果是对象,就直接返回,否则返回 context 对象
return (typeof result === 'object' && result != null) ? result : context;
}
// 实例
var actor = _new(Person, '张三', 28);
构造函数调用过程,英文原文是这样写的。
第7步我的大概理解就是,把obj赋值给this,初始化赋上传来的参数值,把构造函数 (constructFunction)这般执行后的结果作为返回值(后面会隐式return)。
注意第8步,写着如果判断了返回值类型是个对象才会return result。如果这当中我们手动return了一个对象类型的值,它也会把当作返回值来返回。进程到这里就结束了。
而如果是非对象类型,则不会被当作返回值,返回值依旧是之前构造函数调用的结果。
- Let result be the result of calling the [[Call]] internal property of F, providing obj as the this value and providing the argument list passed into [[Construct]] as args.
- If Type(result) is Object then return result.
- Return obj.
请记住使用new
命令只能返回一个对象,要么是实例对象,要么是return语句指定的对象。
看一些例子。
//直接 return
function A(){
this.a = 1
return;
}
//返回 数字类型
function B(){
return 123;
}
//返回 string类型
function C(){
this.a = 3
return "abcdef";
}
//返回 数组
function D(){
return ["aaa", "bbb"];
}
//返回 对象
function E(){
this.a = 5
return {a: 2};
}
//返回 包装类型
function F(){
this.a = 6
return new Number(123);
}
var a = new A() // A {}
var b = new B() // B {}
var c = new C() // C {}
var d = new D() // ["aaa", "bbb"]
var e = new E() // Object {a: 2}
var f = new F() // Number {[[PrimitiveValue]]: 123}
console.log(a,b,c,d,e,f)
![](https://img.haomeiwen.com/i16910815/b50c9409ac34acca.png)
由此可见,在构造函数中 return 基本类型生成的实例都会返回一个对象,如果构造函数内部没有this.xxx
这种初始化操作则返回空对象。且继承了相应构造函数(A、B、C)的prototype(__proto__
指向该构造函数的prototype) ,而我们显式(手动)return对象类型则会直接返回这对象,直接切断了和该构造函数的联系。
function Super (a) {
this.a = a;
return 123;
}
Super.prototype.sayHello = function() {
alert('hello world');
}
function Super_ (a) {
this.a = a;
return {a: 2};
}
Super_.prototype.sayHello = function() {
alert('hello world');
}
var s = new Super(1);
var s_ = new Super_(1);
console.log(s)
console.log(s_)
其他注意点
-
new
命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号。下面两行代码是等价的,但是为了表示这里是函数调用,推荐使用括号。
// 推荐的写法
var v = new Vehicle();
// 不推荐的写法
var v = new Vehicle;
2. 构造函数必须与new
命令一起使用
一个解决办法是,构造函数内部使用严格模式,即第一行加上'use strict'
。这样的话,因为this指向undefined,不能给undefined添加属性。一旦忘了使用new
命令,直接调用构造函数就会报错。
function Fubar(foo, bar){
'use strict';
this._foo = foo;
this._bar = bar;
}
Fubar() //TypeError: Cannot set property '_foo' of undefined
另一个解决办法,构造函数内部判断是否使用new
命令,如果发现没有使用,则直接返回一个实例对象。
function Fubar(foo, bar) {
if (!(this instanceof Fubar)) { //如果不是Fubar的实例
return new Fubar(foo, bar); //再返回一个Fubar的实例对象
}
this._foo = foo;
this._bar = bar;
}
Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1
上面代码中的构造函数,不管加不加new
命令,都会得到同样的结果。
另外,函数内部的new.target
属性可以用来判断是否使用new
调用,如果当前函数是new
命令调用,new.target
指向当前函数f
,否则为undefined
。
function f() {
if (!new.target) {
throw new Error('请使用 new 命令调用!');
}
// ...
}
f() // Uncaught Error: 请使用 new 命令调用!
网友评论