JavaScript中new的运行机制到底是什么?

第一句:JavaScript提供的内置函数和对象都是全局作用域的属性。
第二句:JavaScript中使用new调用函数返回实例对象,该函数会被称为构造函数,作为构造函数是不是必须具备prototype属性对象?
我刚学JavaScript不久,这两句话都是我的观察和猜测,不知是错是对。
我对第一句话的认识
在浏览器环境中,可以做如下操作,

window.Object // 返回 Object()
window.Proxy // 返回 Proxy()
window.isNaN(NaN) // 返回 true

上面代码中的ObjectProxy都是JavaScript提供的内置函数,都可以通过浏览器全局对象window进行访问,我的理解是宿主环境(浏览器引擎)是基于JavaScript语法实现的,那么必然会将JavaScript提供的内置函数,都容纳进浏览器全局对象window中,以便访问,与浏览器相对应的Node环境应该也是如此,不知如此理解是对是错?
第一句和第二句并不具有绝对相关性。
我对第二句话的认识
对于以funcion声明的函数,都会存在prototype属性。
当使用new调用以function定义的函数时,这个函数会被称为构造函数,会发生下面操作:

  • 构造函数运行时引擎会产生一个空对象{}
  • 引擎会将构造函数prototype的引用作为空对象{}的原型。
  • 引擎会将构造函数中的this指向这个新生成的对象。
  • 构造函数如果不返回其它对象,最终都会返回这个新对象。
  • 最重要的一步,引擎会为这个新对象进行初始化,从而调用构造函数的prototype属性中的constructor

function声明的函数不同的是,JavaScript定义的函数有着不同之处,比如ES6新加入的Proxy,使用new调用Proxy会生成实例对象,但是实例对象的原型是Object.prototype而不是Proxyprototype,并且Proxy.prototypeundefined

Object.getPrototypeOf(Proxy) === Function.prototype // true


const proxy = new Proxy({}, {})
Object.getPrototypeOf(proxy) === Object.prototype // true
Proxy.prototype // undefined

同样是函数,一个是function声明,一个是JavaScript内置,都可以使用new调用并返回实例对象,但是前者的prototype有值而后者没有赋值,这个区别使我对new的机制产生了很大的疑惑,难道使用new调用的构造函数可以不需要prototype么?没有prototypeProxy是怎么生成实例对象的呢?
有没有大佬可以指点一二!!!

阅读 2.5k
1 个回答

第一句话理解没有什么问题,在浏览器中就是指window,在NodeJS中指global,都是全局作用域

第二句话就有偏差了,先给结论,new的机制不会特殊,是明确的,一贯的,坚定的。

先说new做了什么
当代码new Foo(...)执行时,会发生以下事情:

  1. 一个继承自Foo.prototype的新对象被创建
  2. 使用指定的参数调用构造函数Foo,并将this绑定到步骤【1】中创建的对象
  3. 由构造函数返回的对象就是new表达式的结果。如果构造函数没有显式返回一个对象,则使用【1】中的值。

重点关注一下【3】,构造函数是可以有返回值的,当返回值为对象时,new的结果是这个对象,而不是【1】创建的对象。
这个可以参看类似的问题我的笔记MDN->new操作符

new的机制,这个对所有函数都是一样的,不存在特殊。


然后再说原型链预期不一致:

const proxy = new Proxy({}, {})
Object.getPrototypeOf(proxy) === Object.prototype // true

这里new Proxy,但是原型链却是指向Object.prototype,原型链不是预期的Proxy.prototype,这就有多种手段可以做到:

  1. 比如上面说的new操作的【3】里面返回了一个对象。这个对象有自己的原型链,跟构造函数就没有关系了。

    可以这样想象Proxy的构造函数:

    function Proxy() {
        return {};
    }
  2. 既然你可以使用Object.getPrototypeOf,那么也同样可以使用Object.setPrototypeOf

再说,Proxy实现:

我们无法看到内置实现,但是我们可以看看其polyfill,看看这个是怎么实现的,就能了解其基本原理:GoogleChrome的Proxy polyfill实现

看其构造函数,摘抄一部分:

let  ProxyPolyfill;
ProxyPolyfill = function(target,  handler) {
    //...
    let proxy = this;
    //...
    if (Object.setPrototypeOf) {
        Object.setPrototypeOf(proxy, Object.getPrototypeOf(target));
        } else if (proxy.__proto__) {
        proxy.__proto__ = target.__proto__;
    }
    //...
    return proxy;
}

可以看到,其polyfill实现确实改变了其原型链指向,使用了我们上面说的第2种方式,重点是指向的是target的原型链

Proxy既然是代理,那你只能代理,而不能改变原来是object,那代理后的依然是object,如果经过代理之后,原型链上的方法都丢了,那还要代理干嘛。
所以,Proxy经过new之后,指向的依然是target的原型链。

推荐问题
宣传栏