这里有一份简洁的前端知识体系等待你查收,看看吧,会有惊喜哦~如果觉得不错,恳求star哈~
概述
我们可以把对象分成几类。
- 宿主对象(host Objects):由 JS 宿主环境提供的对象,它们的行为完全由宿主环境决定。
- 内置对象(Built-in Objects):由 JS 语言提供的对象。
- 固有对象(Intrinsic Objects ):由标准规定,随着 JS 运行时自动创建的对象实例。
- 原生对象(Native Objects):可以由用户通过Array、RegExp等内置构造器或者特殊语法创建的对象。
- 普通对象(Ordinary Objects):由{}语法、Object构造器或者class关键字定义类创建的对象,它能够被原型继承。
下面讲解普通对象之外的对象类型。
宿主对象
首先我们来看看宿主对象。
JS宿主对象千奇百怪,但是前端最熟悉的无疑是浏览器环境中的宿主了。
在浏览器环境中,我们都知道全局对象是window,window上又有很多属性,如document。
实际上,这个全局对象window上的属性,一部分来自 JS 语言,一部分来自浏览器环境。
JS 标准中规定了全局对象属性,w3c的各种标准中规定了Window对象的其它属性。
宿主对象也分为固有的和用户可创建的两种,比如 document.createElement 就可以创建一些dom对象。
宿主也会提供一些构造器,比如我们可以使用new Image来创建img元素.
内置对象·原生对象
我们把 JS 中,能够通过语言本身的构造器创建的对象称作原生对象。
在 JS 标准中,提供了30多个构造器。按照不同应用场景,可以把原生对象分成了以下几个种类。
| 基本类型 | 基础功能与数据结构 | 错误类型 | 二进制操作 | 带类型的数组 |
|---|---|---|---|---|
| Boolean | Array | Error | ArrayBuffer | Float32Array |
| String | Date | EvalError | SharedArrayBuffer | Float64Array |
| Number | RegExp | RangeError | DataView | Int8Array |
| Symbol | Promise | ReferenceError | Int16Array | |
| Object | Proxy | SyntaxError | Int32Array | |
| Map | TypeError | Unit8Array | ||
| WeakMap | URIError | Unit16Array | ||
| Set | Unit32Array | |||
| WeakSet | Unit8ClampedArray | |||
| Function |
通过这些构造器,我们可以用new运算创建新的对象,所以我们把这些对象称作原生对象。
几乎所有这些构造器的能力都是无法用纯JS代码实现的,它们也无法用class/extend语法来继承。
这些构造器创建的对象多数使用了私有字段,例如:
Error: [[ErrorData]]
Boolean: [[BooleanData]]
Number: [[NumberData]]
Date: [[DateValue]]
RegExp: [[RegExpMatcher]]
Symbol: [[SymbolData]]
Map: [[MapData]]
这些字段使得原型继承方法无法正常工作,所以,我们可以认为,所有这些原生对象都是为了特定能力或者性能,而设计出来的“特权对象”。
内置对象·固有对象
固有对象是由标准规定,随着 JS 运行时创建而自动创建的对象实例。
固有对象在任何JS代码执行前就已经被创建出来了,它们通常扮演着类似基础库的角色。我们前面提到的“类”其实就是固有对象的一种。
这里提供代码用来查看JS中的固有对象。
var set = new Set();
var objects = [
eval,
isFinite,
isNaN,
parseFloat,
parseInt,
decodeURI,
decodeURIComponent,
encodeURI,
encodeURIComponent,
Array,
Date,
RegExp,
Promise,
Proxy,
Map,
WeakMap,
Set,
WeakSet,
Function,
Boolean,
String,
Number,
Symbol,
Object,
Error,
EvalError,
RangeError,
ReferenceError,
SyntaxError,
TypeError,
URIError,
ArrayBuffer,
SharedArrayBuffer,
DataView,
Float32Array,
Float64Array,
Int8Array,
Int16Array,
Int32Array,
Uint8Array,
Uint16Array,
Uint32Array,
Uint8ClampedArray,
Atomics,
JSON,
Math,
Reflect
];
objects.forEach(o => set.add(o));
for(var i = 0; i < objects.length; i++) {
var o = objects[i]
for(var p of Object.getOwnPropertyNames(o)) {
var d = Object.getOwnPropertyDescriptor(o, p)
if( (d.value !== null && typeof d.value === "object") || (typeof d.value === "function"))
if(!set.has(d.value))
set.add(d.value), objects.push(d.value);
if( d.get )
if(!set.has(d.get))
set.add(d.get), objects.push(d.get);
if( d.set )
if(!set.has(d.set))
set.add(d.set), objects.push(d.set);
}
}
函数对象与构造器对象
前面介绍了对象的一般分类,在 JS 中,还有一个看待对象的不同视角,这就是用对象来模拟函数和构造器。
事实上,JS 为这一类对象预留了私有字段机制,并规定了抽象的函数对象与构造器对象的概念。
函数对象的定义是:具有[[call]]私有字段的对象,构造器对象的定义是:具有私有字段[[construct]]的对象。
任何对象只需要实现[[call]],它就是一个函数对象,可以去作为函数被调用。而如果它能实现[[construct]],它就是一个构造器对象,可以作为构造器被调用。
对于宿主和内置对象来说,它们实现[[call]](作为函数被调用)和[[construct]](作为构造器被调用)不总是一致的。比如内置对象 Date 在作为构造器调用时产生新的对象,作为函数时,则产生字符串,见以下代码:
console.log(new Date);
console.log(Date())
而浏览器宿主环境中,提供的Image构造器,则根本不允许被作为函数调用。
console.log(new Image);
console.log(Image());//抛出错误
再比如基本类型(String、Number、Boolean),它们的构造器被当作函数调用,则产生类型转换的效果。
值得一提的是,在ES6之后 => 语法创建的函数仅仅是函数,它们无法被当作构造器使用,见以下代码:
new (a => 0) // error
对于用户使用 function 语法或者Function构造器创建的对象来说,[[call]]和[[construct]]行为总是相似的,它们执行同一段代码。
我们看一下示例。
function f () {
return 1;
}
var v = f (); //把f作为函数调用
var o = new f (); //把f作为构造器调用
我们大致可以认为,它们[[construct]]的执行过程如下:
- 以 Object.protoype 为原型创建一个新对象;
- 以新对象为 this,执行函数的[[call]];
- 如果[[call]]的返回值是对象,那么,返回这个对象,否则返回第一步创建的新对象。
这样的规则造成了个有趣的现象,如果我们的构造器返回了一个新的对象,那么new创建的新对象就变成了一个构造函数之外完全无法访问的对象,这一定程度上可以实现“私有”。
function cls () {
this.a = 100;
return {
getValue: () => this.a,
};
}
var o = new cls ();
o.getValue (); //100
//a在外面永远无法访问到
特殊行为的对象
除了上面介绍的对象之外,在固有对象和原生对象中,有一些对象的行为跟正常对象有很大区别。
它们常见的下标运算(就是使用中括号或者点来做属性访问)或者设置原型跟普通对象不同,这里简单总结一下。
- Array:Array的length属性根据最大的下标自动发生变化。
- Object.prototype:作为所有正常对象的默认原型,不能再给它设置原型了。
- String:为了支持下标运算,String的正整数属性访问会去字符串里查找。
- Arguments:arguments的非负整数型下标属性跟对应的变量联动。
- 模块的namespace对象:特殊的地方非常多,跟一般对象完全不一样,尽量只用于import吧。
- 类型数组和数组缓冲区:跟内存块相关联,下标运算比较特殊。
- bind后的function:跟原来的函数相关联。
总结
在这篇文章中,我们介绍了一些不那么常规的对象,并且还介绍了JS中用对象来模拟函数和构造器的机制。