基础概念
什么是对象?
《高程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 | var a={ name:'mike'} |
用api,如 Object.defineProperty,默认writable、enumerable,configurable都是false, value、get、set是undefined
1 | var b = {} |
怎么创建对象?
- new Object()
- 对象字面量, var a ={ name:’li’, age:12}
- 利用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 | var obj = Object.create({d:4},{a:{value:1,enumerable: true},b:{value:2,enumerable: true},c:{value:2,enumerable: false}}) |
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
20o = 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
15function 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)
将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
注意:
- 如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。
- Object.assign 方法只会拷贝 源对象 自身的并且可枚举 的属性到目标对象。继承属性和不可枚举属性是不能拷贝的
- Object.assign 不会跳过那些值为 null 或 undefined 的源对象
- 是浅拷贝
- 异常会打断后续拷贝任务
1 | var o1 = { a: 1, b: 1, c: 1 }; |
Object.defineProperty(obj, prop, descriptor)
直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
注意:
- 数据描述符和存取描述符不能混合使用。
继承属性
访问器属性:如果访问者的属性是被继承的,它的 get 和set 方法会在子对象的属性被访问或者修改时被调用。如果这些方法用一个变量存值,该值会被所有对象共享。
数据属性:不像访问者属性,值属性始终在对象自身上设置,而不是一个原型。然而,如果一个不可写的属性被继承,它仍然可以防止修改对象的属性。如果设置的是对象原型上的属性,它的 get 和set 方法会在实例的属性被访问或者修改时被调用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function 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
16function 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
10var 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
12var 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 | o = new Object(); |
Object.keys()
返回一个由一个给定对象自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致 。1
2var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']
Object.values()
返回一个给定 对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。1
2var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]
Object.entries()
返回一个给定 对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。
1 | const obj = { foo: 'bar', baz: 42 }; |
扩展(ES6)
reflect
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。
Reflect对象一共有 13 个静态方法。
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- 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改写,就是下面这样。
注意:
- 类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
- 类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行
- 类不存在变量提升(hoist),这一点与 ES5 完全不同
- 私有方法是常见需求,但 ES6 不提供,只能通过变通方法模拟实现。_表示私有方法
- 与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
- 静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
1
2
3
4
5
6
7
8
9
10function 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
10class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}