【JS基础系列】JS对象

时间:2020-9-8 作者:admin

1. JavaScript 对象的特征

在 JavaScript 中,对象的状态和行为其实都被抽象为了属性。

下面这段代码其实就展示了普通属性和函数作为属性的一个例子,其中o 是对象,d 是一个属性,而函数 f 也是一个属性,尽管写法不太相同,但是对 JavaScript 来说,d 和 f 就是两个普通属性。

var o = { 
    d: 1,
    f() {
        console.log(this.d);
    }    
};

JavaScript 中对象独有的特色是:对象具有高度的动态性,这是因为 JavaScript 赋予了使用者在运行时为对象添改状态和行为的能力。

JavaScript 允许运行时向对象添加属性,这就跟绝大多数基于类的、静态的对象设计完全不同。

var o = { a: 1 };
o.b = 2;
console.log(o.a, o.b); //1 2

为了提高抽象能力,JavaScript 的属性被设计成比别的语言更加复杂的形式,它提供了数据属性和访问器属性(getter/setter)两类。

2. JavaScript 对象的两类属性

对 JavaScript 来说,属性并非只是简单的名称和值,JavaScript 用一组特征(attribute)来描述属性(property)。

1. 数据属性

数据属性具有四个特征:

  • value:就是属性的值。
  • writable:决定属性能否被赋值。
  • enumerable:决定 for in 能否枚举该属性。
  • configurable:决定该属性能否被删除或者改变特征值。

2. 访问器(getter/setter)属性

访问器属性的四个特征:

  • getter:函数或 undefined,在取属性值时被调用。
  • setter:函数或 undefined,在设置属性值时被调用。
  • enumerable:决定 for in 能否枚举该属性。
  • configurable:决定该属性能否被删除或者改变特征值。

访问器属性使得属性在读和写时执行代码,它允许使用者在写和读属性时,得到完全不同的值,它可以视为一种函数的语法糖。

我们通常用于定义属性的代码会产生数据属性,其中的 writable、enumerable、configurable 都默认为 true。我们可以使用内置函数 getOwnPropertyDescripter 来查看:

var o = { a: 1 };
o.b = 2;

//a和b皆为数据属性
Object.getOwnPropertyDescriptor(o,"a");
// {value: 1, writable: true, enumerable: true, configurable: true}

Object.getOwnPropertyDescriptor(o,"b");
// {value: 2, writable: true, enumerable: true, configurable: true}

如果我们要想改变属性的特征,或者定义访问器属性,我们可以使用 Object.defineProperty

var o = { a: 1 };
Object.defineProperty(o, "b", {value: 2, writable: false, enumerable: false, configurable: true});

//a和b都是数据属性,但特征值变化了
Object.getOwnPropertyDescriptor(o,"a"); 
// {value: 1, writable: true, enumerable: true, configurable: true}

Object.getOwnPropertyDescriptor(o,"b"); 
// {value: 2, writable: false, enumerable: false, configurable: true}

o.b = 3;
console.log(o.b); 
// 2
// 由于b属性的writable属性被设置为false了,表示属性不能被赋值,所以b的值依然为2

在创建对象时,也可以使用 get 和 set 关键字来创建访问器属性,访问器属性跟数据属性不同,每次访问属性都会执行 getter 或者 setter 函数

var o = { get a() { return 1 } };

console.log(o.a); // 1

3. JavaScript 中的对象分类

  • 宿主对象:由 JavaScript 宿主环境提供的对象,它们的行为完全由宿主环境决定。
  • 内置对象:由 JavaScript 语言提供的对象。
    • 固有对象:由标准规定,随着 JavaScript 运行时创建而自动创建的对象实例。
    • 原生对象:可以由用户通过 Array、RegExp 等内置构造器或者特殊语法创建的对象。
    • 普通对象:由{}语法、Object 构造器或者 class 关键字定义类创建的对象,它能够被原型继承。

1. 宿主对象

在浏览器环境中,我们都知道全局对象是 window,window 上又有很多属性,如 document。这个全局对象 window 上的属性,一部分来自 JavaScript 语言,一部分来自浏览器环境。
JavaScript 标准中规定了全局对象属性,W3C 的各种标准中规定了 Window 对象的其它属性。

2. 内置对象·固有对象

固有对象在任何 JavaScript 代码执行前就已经被创建出来了,它们通常扮演者类似基础库的角色。

JavaScript 语言规定了全局对象的属性。

  • 三个值:Infinity、NaN、undefined。
  • 九个函数: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、Typed Array、Float32Array、Float64Array、Int8Array、Int16Array、Int32Array、UInt8Array、UInt16Array、UInt32Array、UInt8ClampedArray。
  • 四个用于当作命名空间的对象:Atomics、JSON、Math、Reflect

使用广度优先搜索,查找这些对象所有的属性和 Getter/Setter,就可以获得 JavaScript 中所有的固有对象。

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);
    }
}

3. 内置对象·原生对象

JavaScript 中,能够通过语言本身的构造器创建的对象称作原生对象。

【JS基础系列】JS对象

通过这些构造器,我们可以用 new 运算创建新的对象。

几乎所有这些构造器的能力都是无法用纯 JavaScript 代码实现的,它们也无法用 class/extend 语法来继承。这些原生对象都是为了特定能力或者性能,而设计出来的“特权对象”。

用对象来模拟函数与构造器:函数对象与构造器对象

函数对象的定义是:具有[[call]]私有字段的对象,构造器对象的定义是:具有私有字段[[construct]]的对象。

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

任何对象只需要实现[[call]],它就是一个函数对象,可以去作为函数被调用。而如果它能实现[[construct]],它就是一个构造器对象,可以作为构造器被调用。

用户用 function 关键字创建的函数必定同时是函数和构造器。不过,在 ES6 之后 => 语法创建的函数仅仅是函数,它们无法被当作构造器使用,

不过对于宿主和内置对象来说,它们实现[[call]](作为函数被调用)和[[construct]](作为构造器被调用)不总是一致的。比如

  • 内置对象 Date 在作为构造器调用时产生新的对象,作为函数时,则产生字符串;
  • 浏览器宿主环境中,提供的 Image 构造器,则根本不允许被作为函数调用;
  • 基本类型(String、Number、Boolean),它们的构造器被当作函数调用,则产生类型转换的效果。
typeof new Date()
// "object"

typeof Date()
// "string"

console.log(new Image()); 
console.log(Image());//抛出错误

【JS基础系列】JS对象

特殊行为的对象

特殊行为的对象除了上面介绍的对象之外,在固有对象和原生对象中,有一些对象的行为跟正常对象有很大区别。

它们常见的下标运算(就是使用中括号或者点来做属性访问)或者设置原型跟普通对象不同,这里简单总结一下:

  • Array:Array 的 length 属性根据最大的下标自动发生变化。
  • Object.prototype:作为所有正常对象的默认原型,不能再给它设置原型了。
  • String:为了支持下标运算,String 的正整数属性访问会去字符串里查找。
  • Arguments:arguments 的非负整数型下标属性跟对应的变量联动。
  • 模块的 namespace 对象:特殊的地方非常多,跟一般对象完全不一样,尽量只用于 import 吧。
  • 类型数组和数组缓冲区:跟内存块相关联,下标运算比较特殊。
  • bind 后的 function:跟原来的函数相关联。
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。