js对象API深入学习

基础概念

什么是对象?

《高程3》,对象是无序属性的集合,其属性可以包含基本值、对象或者函数。可以把对象想象成散列表,是一组名值对,其中值可以是数据或者函数。
属性分为数据属性和访问器属性两种。
数据属性包含1个数据值的位置,在这个位置可以读取和写入值,数据属性有4个描述行为的特性。
访问器属性不包含数据值,包含一对getter和setter函数,读取访问器属性,会调用getter,写入访问器属性,调用setter,这个函数负责决定如何处理数据,访问器属性有4个特性。

属性特性

- Enumerable Configurable Writable Value Getter Setter
数据属性 Yes Yes Yes Yes
访问器属性 Yes Yes Yes Yes

Configurable: 能否通过delete删除属性,能否修改属性的特性,或者能否把属性修改为数据属性
Enumerable : 对象的属性是否可以在 for…in 循环和 Object.keys() 中被枚举
Wrirable :能否修改属性的值
Value:这个属性的数据值
Get:读取属性的时候调用的函数。
Set :写入属性的时候调用的函数。

属性的默认值

当用字面量创建对象,默认writable、enumerable,configurable都是true

1
2
3
var a={ name:'mike'}
Object.getOwnPropertyDescriptor(a,'name')
{value: "mike", writable: true, enumerable: true, configurable: true}

用api,如 Object.defineProperty,默认writable、enumerable,configurable都是false, value、get、set是undefined

1
2
3
var b = {}
Object.defineProperty(b,'name',{})
{value: undefined, writable: false, enumerable: false, configurable: false}

怎么创建对象?

  1. new Object()
  2. 对象字面量, var a ={ name:’li’, age:12}
  3. 利用api, Object.create(), Object.difineProperty(), Object.defineProperties()

API分类整理

创建对象:

  • Object.create (操作对象原型和对象自身可枚举属性)

操作属性:

  • Object.assign (合并对象属性,浅拷贝,源对象自身可枚举属性
  • Object.defineProperty (添加或修改属性)
  • Object.defineProperties
  • Object.getOwnPropertyDescriptor (自有属性对应的属性描述符)
  • Object.prototype.hasOwnProperty (自身属性中是否具有指定的属性)
  • propertyIsEnumerable

遍历:

  • for … in (包括原型链上)
  • Object.keys() (自身可枚举属性组成的数组)
  • Object.values() (对象自身的所有可枚举属性值的数组)
  • Object.entries() (自身可枚举属性的键值对数组)

属性的可枚举性和遍历

目前,有四个操作会忽略enumerable为false的属性。另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。

for…in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

ES6 一共有 5 种方法可以遍历对象的属性。
(1)for…in
for…in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

(2)Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

- 方法 继承 区分枚举
1 for…in 枚举
2 Object.keys(obj) 枚举
3 Object.getOwnPropertyNames(obj) 所有
4 Object.getOwnPropertySymbols(obj) 所有 Symbol 属性的键名
5 Reflect.ownKeys(obj) 所有
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var obj = Object.create({d:4},{a:{value:1,enumerable: true},b:{value:2,enumerable: true},c:{value:2,enumerable: false}})

for(let i in obj){
console.log(i,obj[i])
}
// a 1
// b 2
// d 4


for(let i of Object.keys(obj)){
console.log(i,obj[i])
}
// a 1
// b 2

for(let i of Object.getOwnPropertyNames(obj)){
console.log(i,obj[i])
}
// a 1
// b 2
// c 3

for(let i of Reflect.ownKeys(obj)){
console.log(i,obj[i])
}
// a 1
// b 2
// c 3

API详细

Object.create(proto, [propertiesObject])

创建一个新对象,使用现有的对象来提供新创建的对象的proto
propertiesObject,可选。如果没有指定为undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数

创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
o = Object.create(Object.prototype, {
// foo会成为所创建对象的数据属性
foo: {
writable:true,
configurable:true,
value: "hello"
},
// bar会成为所创建对象的访问器属性
bar: {
configurable: false,
get: function() { return 10 },
set: function(value) {
console.log("Setting `o.bar` to", value);
}
}
});
Object.getOwnPropertyDescriptors(o)
//bar: {get: ƒ, set: ƒ, enumerable: false, configurable: false}
//foo: {value: "hello", writable: true, enumerable: false, configurable: true}
//__proto__: Object

实现类式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}

// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function() {
// do a thing
};

Object.assign(target, …sources)

将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
注意:

  1. 如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。
  2. Object.assign 方法只会拷贝 源对象 自身的并且可枚举 的属性到目标对象。继承属性和不可枚举属性是不能拷贝的
  3. Object.assign 不会跳过那些值为 null 或 undefined 的源对象
  4. 是浅拷贝
  5. 异常会打断后续拷贝任务
1
2
3
4
5
6
var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };

var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

Object.defineProperty(obj, prop, descriptor)

直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
注意:

  1. 数据描述符和存取描述符不能混合使用。
  2. 继承属性
    访问器属性:如果访问者的属性是被继承的,它的 get 和set 方法会在子对象的属性被访问或者修改时被调用。如果这些方法用一个变量存值,该值会被所有对象共享。
    数据属性:不像访问者属性,值属性始终在对象自身上设置,而不是一个原型。然而,如果一个不可写的属性被继承,它仍然可以防止修改对象的属性。如果设置的是对象原型上的属性,它的 get 和set 方法会在实例的属性被访问或者修改时被调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
      function myclass() {
    }

    var value;
    Object.defineProperty(myclass.prototype, "x", {
    get() {
    return value;
    },
    set(x) {
    value = x;
    }
    });

    var a = new myclass();
    var b = new myclass();
    a.x = 1;
    console.log(b.x); // 1

    解决方案,这可以通过将值存储在另一个属性中固定。在 get 和 set 方法中,this 指向某个被访问和修改属性的对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
      function myclass() {
    }

    Object.defineProperty(myclass.prototype, "x", {
    get() {
    return this.stored_x;
    },
    set(x) {
    this.stored_x = x;
    }
    });

    var a = new myclass();
    var b = new myclass();
    a.x = 1;
    console.log(b.x); // undefined

Object.defineProperties(obj, props)

直接在一个对象上定义新的属性或修改现有属性,并返回该对象。

Object.getOwnPropertyDescriptor(obj, prop)

返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性。

1
2
3
4
5
6
7
8
9
10
var o

o = { get foo() { return 17; } };
d = Object.getOwnPropertyDescriptor(o, "foo");
// d {
// configurable: true,
// enumerable: true,
// get: /*the getter function*/,
// set: undefined
// }

Object.prototype.hasOwnProperty()

返回一个布尔值,指示对象自身属性中是否具有指定的属性。
遍历一个对象的所有自身属性

1
2
3
4
5
6
7
8
9
10
11
12
var buz = {
fog: 'stack'
};

for (var name in buz) {
if (buz.hasOwnProperty(name)) {
alert("this is fog (" + name + ") for sure. Value: " + buz[name]);
}
else {
alert(name); // toString or something else
}
}

1
2
3
4
5
6
7
8
9
10
11
o = new Object();
o.prop = 'exists';

function changeO() {
o.newprop = o.prop;
delete o.prop;
}

o.hasOwnProperty('prop'); // 返回 true
changeO();
o.hasOwnProperty('prop'); // 返回 false

Object.keys()

返回一个由一个给定对象自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致 。

1
2
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']

Object.values()

返回一个给定 对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

1
2
var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]

Object.entries()

返回一个给定 对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

1
2
3
4
5
6
7
const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

const obj = { a: 5, b: 7, c: 9 };
for (const [key, value] of Object.entries(obj)) {
console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
}

扩展(ES6)

reflect

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。

Reflect对象一共有 13 个静态方法。

  1. Reflect.apply(target, thisArg, args)
  2. Reflect.construct(target, args)
  3. Reflect.get(target, name, receiver)
  4. Reflect.set(target, name, value, receiver)
  5. Reflect.defineProperty(target, name, desc)
  6. Reflect.deleteProperty(target, name)
  7. Reflect.has(target, name)
  8. Reflect.ownKeys(target)
  9. Reflect.isExtensible(target)
  10. Reflect.preventExtensions(target)
  11. Reflect.getOwnPropertyDescriptor(target, name)
  12. Reflect.getPrototypeOf(target)
  13. Reflect.setPrototypeOf(target, prototype)

proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

1
var proxy = new Proxy(target, handler);

class

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。

注意:

  1. 类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
  2. 类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行
  3. 类不存在变量提升(hoist),这一点与 ES5 完全不同
  4. 私有方法是常见需求,但 ES6 不提供,只能通过变通方法模拟实现。_表示私有方法
  5. 与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
  6. 静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function Point(x, y) {
    this.x = x;
    this.y = y;
    }

    Point.prototype.toString = function () {
    return '(' + this.x + ', ' + this.y + ')';
    };

    var p = new Point(1, 2);

class语法

1
2
3
4
5
6
7
8
9
10
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}