【《你不知道的JS(上卷②)》】三、对象

三、对象:

一)、语法:

对象有两种形式定义:

声明(文字)形式

var myObj = {
    key: value
    // ...
};

构造形式

var myObj = new Object();
myObj.key = value;
  • 在声明形式中可以添加多个键/值对,但是在构造形式中必须逐个添加属性。

1、类型:

​ 对象是JS的基础,JS中一共有六种主要类型。

  • string
  • number
  • boolean
  • null
  • undefined
  • object

​ 前五个被称为 简单基本类型,本身并不是对象,执行type of null会返回obejct,这属于JS语言的一个bug。因此 JS中并不是万物皆是对象

​ JS中有许多特殊的对象子类型,称之为 复杂基本类型

  • 函数就是对象的一个子类型(即可调用的对象)。JS中函数被称作“一等公民”,本质上就是因为函数就是一个对象,可以用来向另一个函数传递参数等。
  • 数组也是对象的一个子类型,具备一些额外的行为。

2、内置对象:

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

​ 内置对象的表现形式很像其他语言的类型(type)或者类(class),(比如Java中的String类)。但是在JS中,这些只是一些内置函数(可以当作构造函数使用(参见本卷this详解中new绑定))。

var strPrimitive = "I am a string";
console.log(typeof strPrimitive);  // string
console.log(strPrimitive instanceof String);  // false

var strObject = new String("I am a string");
console.log(typeof strObject);  // object
console.log(strObject instanceof String);  // true

console.log(strPrimitive.charAt(3));  // m
  • ​ 虽然strPrimitive在声明时只是一个字面量,只有值,而没有String的属性。但是在必要时,引擎会自动将字符串字面量转换成一个String对象。对其他的内置类型也是如此。

三)、内容:

​ 对象的内容是由一些存储在特定命名位置的(任意类型的)值组成的,我们称之为属性。

  • 实际上这些值并不是存储在对象内部,在引擎内部,对象容器内部存储引用,指向这些值真正的存储位置。

属性访问、键访问:

var myObject = {
  a: 2
};

// 属性访问:
console.log(myObject.a);  // 2
// 键访问:
console.log(myObject["a"]);  // 2
  • 属性名要求必须满足标识符的命名规范,而键访问可以是任何满足UTF的字符串的属性名。

1、可计算属性名:

主要用于ES6中的符号(Symbol)。

2、属性与方法:

​ 准确来说,函数并不会属于一个对象,因此JS中的函数并不是“方法”。(即使具有this引用)。

3、数组:

​ 数组也支持字符串作为键,但是使用数值下标经过了优化。

4、复制对象:

浅拷贝/深拷贝:

function anotherFunction() {
  // ...
}

var anotherObject = {
  c: true
};

var anotherArray = [];

var myObject = {
a: 2,
b: anotherObject,  // 引用
c: anotherArray,
d: anotherFunction 
};

浅拷贝在复制myObject的同时,也会去复制a的值,而b、c、d的值仍然是引用。

深拷贝则会同时复制b、c、d的值。(深拷贝可能会导致循环引用,导致死循环)

  • 对于JSON安全的对象,可以使用:

    var newObj = JSON.parse(JSON.stringify(someObj));
    
  • ES6中定义了 Object.assign(..)方法来实现浅拷贝。方法的第一个参数是 目标对象,之后可以跟一个或多个 源对象。它会遍历源对象的所有 可枚举的自由键并把它们复制到目标对象。

5、属性描述符:

6、不变性:

7、[[Get]]:

​ 属性访问并不是简单的在对象中查找该属性。而是实现了[[Get]]操作(类似于函数调用[[Get]]())。会在对象中查找是否有名称相同的属性,如果有则返回该属性的值,如果没有则沿原型链查找。如果还是找不到则会返回undefined(如果该变量在当前词法作用域中没有定义,则会抛出ReferenceError异常)。

8、[[Put]]:

[[Put]]流程:

  1. 属性是否是访问描述符?如果是并且存在setter就调用setter。
  2. 属性的数据描述符中writable是否是false?如果是,在非严格模式下静默失败,在严格模式下抛出TypeError异常。
  3. 如果都不是,将该值设置为属性的值。

9、Getter和Setter:

​ getter和setter是隐藏函数,分别会在获取属性值、设置属性值时会被调用。

10、存在性:

​ 当类似 myObject.a这样访问属性时返回undefined,可能时属性不存在,但是也有可能该属性存储的值就是undefined。

var myObject = {
  a: 2
};

console.log("a" in myObject)  // true
console.log("b" in myObject)  // false
console.log(myObject.hasOwnProperty("a"));  // true
console.log(myObject.hasOwnProperty("b"));  // false
  • in操作符会检查属性是否在对象及其[[Prototype]]原型链中。

  • hasOwnProperty(..)只会检查是否在对象中。

  •   Object.prototype.hasOwnProperty.call(myObject, "a");
    
    • 借用基础的hasOwnProperty方法并把它显式绑定到myObject上。
  • 枚举 enumerable

四)、遍历:

  • for .. in循环可以用来遍历对象的可枚举属性列表。

  • 数值索引的数组,可以用标准的for循环来遍历值。

  • for .. of循环可以直接遍历数组的值而不是数组下标。

    •   arr = [1, 2, 3]
        
        for (const val of arr) {
          console.log(val)
        };
      
    • of循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有值。

    • 数组有内置的 @@iterator,因此of可以直接应用在数组上。我们可以使用内置的@@iterator来手动遍历数组:

      var myArray = [1, 2, 3]
      var it = myArray[Symbol.iterator]();
      
      console.log(it.next());  // { value: 1, done: false }
      console.log(it.next());  // { value: 2, done: false }
      console.log(it.next());  // { value: 3, done: false }
      console.log(it.next());  // { value: undefined, done: true }
      
    • 普通对象并没有内置的@@iterator,但是可以自己添加。

辅助迭代器:

​ 接受回调函数并把它应用到数组的每个元素上,唯一的区别就是它们对于回调函数返回值的处理方式不同。

  • forEach(..):遍历数组中的所有值并忽略回调函数的返回值。
  • every(..):会一直运行直到回调函数返回false。
  • some(..):会一直运行知道回调函数返回true。
posted @ 2020-06-17 22:41  macguz  阅读(162)  评论(0编辑  收藏  举报