javascript:新关键字在内部做了什么

问题描述

我知道可能已经有一百万个类似的问题,例如

但是请听到我的声音。

代码

let f = function(){console.log(".f.")};
fn = new f();

// Now:
typeof fn === "object" //true
//! fn() //TypeError: fn is not a function
//! new fn() //TypeError: fn is not a constructor

一般的问题是:是否可以通过操纵函数fn来创建“新的”对象f

该问题细分为“ new”关键字的内部。

根据MDN文档,我的疑问是,当使用new关键字时,会调用constructorclass中的function。但是,即使fn.__proto__.constructor === f与其他所有JavaScript functions一样,fn的类型为'object'(我们可以将其更改为'function'吗?),并且new fn()引发TypeError。

我们甚至可以通过以下方式添加更多信息:

fn.constructor = f.constructor 
fn.__proto__ = f.__proto__
fn.prototype = f.prototype
// f.constructor === Function //true
//! fn.call(this) //fn.call is not a function

fn()仍然不起作用,new fnnew fn()也不起作用。

为什么?

解决方法

JavaScript中的新对象是:

  1. 使用function关键字创建的函数(不包括生成器函数)
  2. 类(可以视为函数)
  3. 绑定函数奇异对象(“绑定函数”)
  4. 一些宿主对象
  5. 代理(如果应用于上述其中之一)

我知道这是因为new operator的唯一对象类型与are "constructors"(规范术语)一起使用。构造函数是具有[[Construct]]内部方法的对象,您可以搜索ECMAScript规范以找出具有[[Construct]]内部方法的对象。

因此,要使构造函数的结果可更新,必须返回上面列出的一种对象。

请注意,规范specifically says规定所有构造函数都是定义函数,因为它们必须支持[[Call]]内部方法(另请注意以下有关宿主对象的警告)。

如果您想变得非常高级,那么您可能有兴趣了解host objects do not appear to share the ordinary limitations for constructors(大概是出于旧版Web兼容性的原因),但这是例外。

.constructor属性的说明

声明了可启用的功能f时,two objects are created:功能对象f本身,以及.prototype自身属性{上的默认对象{1}}。运行时会自动将此默认f对象的.constructor属性设置为.prototype。我相信课程的工作方式非常相似。请注意,选择此属性的名称为“原型”这一事实使讨论原型在JavaScript中非常混乱(因为它与函数的f不同)。

位于[[prototype]]属性上的对象的constructor自有财产,从未被任何内置函数或操作(据我所知)读取。我从JavaScript的早期开始就将其视为残余物-它的初衷是为了在构建作为开发人员提供的对象的“类”之间保持链接。主机环境(例如浏览器)有时会使用它来推断对象的“类型”,以便与用户进行通信(例如控制台输出),该属性是可写的,因此不可靠。

.prototype运算符执行的步骤

在较高级别上,当对构造函数调用new时,将发生以下步骤(spec contains full details):

  1. 创建了一个新对象new
  2. o的{​​{1}}(“原型”)被设置为构造函数的[[Prototype]]属性的值(请注意,这意味着o属性是继承的通过新对象)
  3. 构造函数主体的目标(即.prototype)设置为.constructor
  4. 构造函数以上面定义的this值运行
  5. 如果没有显式的 object-type 返回值,则默认返回o
,

范围:我们仅在new上检查Function 。因为这是最令人困惑的部分。在new上调用Class会产生与其他主要OOP语言相似的结果。

原始问题可以分解为以下两个问题:

  1. 调用new关键字时的详细构建过程是什么?

  2. JavaScript如何确定对象是否可调用? (感谢@BenAston提到new关键字只能与一组有限的对象一起使用(例如,前缀为ClassFunction的对象))


  1. 第一个问题的答案:

返回到MDN Document

执行代码new Foo(...)时,会发生以下情况:

  1. 已创建一个继承自Foo.prototype的新对象。
  2. 使用指定的参数调用构造函数Foo,并将其绑定到新创建的对象。 new Foo等同于new Foo(),即,如果未指定参数列表,则调用Foo时不带参数。
  3. 构造函数返回的对象(非null,false,3.1415或其他原始类型)成为整个新表达式的结果。如果构造函数未显式返回对象,则使用在步骤1中创建的对象。(通常,构造函数不返回值,但是如果他们想覆盖常规值,则可以选择这样做。对象创建过程。)

这些词可能含糊不清, 但是PoC代码如下:

// Case1,function has no return value; 
// A new object is created,f0n.__proto__ === f0.prototype
let f0 = function() {};
f0.prototype.f0p = "f0p";

let f0n = new f0();
console.log(f0n) // f0n is a new Object,inheriting from f0.prototype
console.log(f0n.__proto__.f0p); // "f0p"

// Case3,function has an explicit return value,the value is an object
// (not null,false,3.1415 or other primitive types); 
// the return value becomes the new object value.
let f3 = function() {
  return {
    "f3": "f3"
  }
};
f3.prototype.f3p = "f3p";

let f3n = new f3();
console.log(f3n) // {f3: "f3"}
// f3n is an Object,the return value of its constructor function `f3`
console.log(f3n.__proto__.f3p); // undefined

// Case4 (or Case1 again),function has an **implicit** return value.
let f4 = function(a) {
  return (a + a)
};
f4.prototype.f4p = "f4p";

let f4n = new f4();
console.log(f4n.__proto__.f4p); // "f4p"

2。回答第二个问题:

我仍然不知道JavaScript如何确定对象是否可调用。答案应该隐藏在ECMAScripts spec中。 (感谢@BenAston指出)

假设只有Function是可调用的,这可能是合法的。以下文章提供了一种解决方法: How to make an object callable

  • 额外:如何返回可赎回债券?

使用Case3,let f = Function(){return Function(){}} 由于返回值是非原始显式对象,因此它成为new指令的结果。结果是一个函数,然后可以调用它。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...