JavaScript 函数和构造器

· 656 words · 4 min

JS 中的对象可以分为:

比如全局对象 window 上的一些属性就是由宿主对象提供的。某些宿主对象也可以被用户自己创建, 比如 document.createElement 就可以创建一些 DOM 对象。

我们常见的数据类型的构造器属于内置的原生对象,比如 NumberStringBooleanObject 等, 还有一些其他的比如 ErrorTypeErrorArrayBufferSharedArrayBufferInt8ArrayUnit8Array 等。 这些构造器不是由 JS 实现的,因此无法用 classextends 语法进行继承。

前面说过,JS 可以使用对象来模拟函数和构造器。JS 规定了对象函数的定义是具有 [[call]] 私有字段的对象, 而构造器对象的定义是具有 [[construct]] 私有字段的对象。JS 使用对象模拟了函数, 任何对象只要实现了 [[call]],就可以作为一个函数进行调用。如果实现了 [[construct]],就可以作为一个构造器被调用。

[[call]] 私有字段是引擎中定义的函数,接受 this 值和调用参数,并且会产生域的切换。

但是用户(程序员)使用 function 创建的对象必定同时是函数和构造器,且函数和构造器的表现是不一样的。 但是对于宿主和内置对象来说,实现函数(即 [[call]])和构造器(即 [[construct]])是可以不同的, 比如 Date 在作为构造函数使用时,产生新的对象,但是作为函数调用时,产生新的字符串。

const date1 = new Date();
const date2 = Date();

console.log(Object.prototype.toString.call(date1)); // '[object Date]'
console.log(Object.prototype.toString.call(date2)); // '[object String]'

再比如 NumberString 等作为函数被调用时,具有类型转换的效果。 但是 ES6 之后的箭头函数(Arrow funciton)只能作为函数被调用,不能作为构造器使用。

function fn() {
	return 1;
}

var f1 = fn();
var f2 = new fn();

console.log(Object.prototype.toString.call(f1)); // '[object Number]'
console.log(Object.prototype.toString.call(f2)); // '[object Object]'

我们可以认为 [[construct]] 在执行的过程中如下:

因此如果构造器返回了一个新的对象,那么 new 创建的新对象就变成了一个除构造函数之外完全无法访问的对象。

function cls() {
	this.a = 100;
	return {
		getA: () => this.a
	}
}

var o = new cls();

console.log(o.getA()); // 100
console.log(o.a); // undefined,a 挂在了内部新建的对象上,而不是返回的对象
From 极客时间