· 656 words · 4 min
JS 中的对象可以分为:
比如全局对象 window
上的一些属性就是由宿主对象提供的。某些宿主对象也可以被用户自己创建,
比如 document.createElement
就可以创建一些 DOM 对象。
我们常见的数据类型的构造器属于内置的原生对象,比如 Number
、String
、Boolean
、Object
等,
还有一些其他的比如 Error
、TypeError
、ArrayBuffer
、SharedArrayBuffer
、Int8Array
、Unit8Array
等。
这些构造器不是由 JS 实现的,因此无法用 class
的 extends
语法进行继承。
前面说过,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]'
再比如 Number
、String
等作为函数被调用时,具有类型转换的效果。
但是 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]]
在执行的过程中如下:
Object.prototype
为原型创建一个对象。this
,执行函数的 [[call]]
。[[call]]
返回值为对象,则返回该对象。否则返回第一步新建的对象。因此如果构造器返回了一个新的对象,那么 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 挂在了内部新建的对象上,而不是返回的对象