对象是 new 出来的,那到底发生了什么?

什么,看到这个标题,大吃一惊,程序员竟然有对象?当然有啦,这都什么年代了。不信,你来看看有了对象之后,面试官就会问你到底发生了什么?

专业一点讲述:

new 是用来实例化一个类的,然后在内存中分配一个实例对象。

我们先来看看一个例子:

function Person(name) {
    this.name = name;
}
Person.hairColor = "black";
Person.prototype.say = function() {
    console.log("My name is " + this.name);
};
var ken = new Person("人生代码");

console.log(
    ken.name, // "人生代码",
      ken.hairColor, // undefined
      ken.height // undefined
);
ken.say(); // "My name is 人生代码"

console.log(
    Person.name, // "Person"
      Person.hairColor // "black"
);
Person.say(); // Person.say is not a function

这段代码发生了什么?接下来就来揭晓答案

重点解析

第8行代码是关键:

var ken = new Person("人生代码");

Person 本来就是个普通的函数,只不过如果给他加了 new 操作,就变成了构造函数。

那么 JS 引擎在解析代码的时候,内部会做很多处理,伪代码如下:

new Person('人生代码') = {
  var obj = {}; // 定义对象
  var proto = Object.create(Person.prototype); // 复制原型
  obj.__proto__ = proto; // 建立原型链
  // obj->Person.prototype->Object.protorype->null
  var res = Person.call(obj, '人生代码'); // 相当于 obj.Person('人生代码')
  // 如果无返回值或者返回一个非对象值,则将obj返回作为新对象:
    return typeof res === 'object' ? result || obj : obj;
}

我们可以得出以下几点:

  • obj.name 是在 Person.call(obj, '人生代码') 之后才赋值的
  • 等到 obj 经返回被赋给 ken 之后,ken.name 就是 '人生代码' 了

所以我们可以画出以下原型链:

  • ken.name: 临时变量 objnameobj 经返回被赋给 kenken 的一些属性由此而来。
  • ken.hairColorken 实例对象 先查找自身的 hairColor,没有找到便会沿着原型链查找,在上述例子中,我们仅在 Person 对象上定义了 hairColor,并没有在其原型链上定义,因此找不到。
  • ken.heightken 实例对象 先查找自身的 height,没有找到便会沿着原型链查找,原型链上也没有,因此找不到。
  • ken.sayken 会先查找自身的 say 方法,没有找到便会沿着原型链查找,在上述例子中,我们在 Person.prototype 上定义了 say,因此在原型链上找到了say 方法。
  • 另外,在 say 方法中还访问 this.name,这里的 this 指的是其调用者。如果 ken 调用 sayken 就是调用者,因此输出ken.name的值。

总结

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new 关键字会进行如下的操:

  • 创建一个空的简单JavaScript对象(即{});
  • 链接该对象(即设置该对象的构造函数)到另一个对象 ;
  • 将步骤1新创建的对象作为this的上下文 ;
  • 如果该函数没有返回对象,则返回this

代码实现请参考以下链接:

https://github.com/sisterAn/JavaScript-Algorithms/issues/71

KenNaNa
89 声望16 粉丝