美文网首页
构造函数实例化过程详解(原理)及为何不要显式使用return

构造函数实例化过程详解(原理)及为何不要显式使用return

作者: AizawaSayo | 来源:发表于2020-07-17 09:55 被阅读0次

首先我们看下new一个构造函数时具体执行了什么操作,假设这个构造函数名为Person。

  1. 在内存中新建一个空对象。这里用p代替这个对象。
  2. 将这个空对象的原型__proto__,指向构造函数的prototype属性。即空对象继承了构造函数的原型。
    p.__proto__ = Person.prototype
  3. 将这个空对象赋值给函数内部的this关键字,即this变量指向这个空对象。
    Person.call(p)//解释上述this指向的问题
  4. 开始执行构造函数内部的代码。如果有传参,则根据定义的键值和传入的参数,依次给这个空对象添加上键值对。
  5. 在构造函数语句末尾隐式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了一个对象类型的值,它也会把当作返回值来返回。进程到这里就结束了。
而如果是非对象类型,则不会被当作返回值,返回值依旧是之前构造函数调用的结果。

  1. 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.
  2. If Type(result) is Object then return result.
  3. 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) 
打印结果

由此可见,在构造函数中 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_)

其他注意点

  1. 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 命令调用!

相关文章

网友评论

      本文标题:构造函数实例化过程详解(原理)及为何不要显式使用return

      本文链接:https://www.haomeiwen.com/subject/urgphktx.html