三、对象:
一)、语法:
对象有两种形式定义:
声明(文字)形式:
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]]流程:
- 属性是否是访问描述符?如果是并且存在setter就调用setter。
- 属性的数据描述符中writable是否是false?如果是,在非严格模式下静默失败,在严格模式下抛出TypeError异常。
- 如果都不是,将该值设置为属性的值。
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。