JavaScript 基础 - 简单对象和属性

创建简单对象

1,使用对象字面量

1
2
3
4
5
6
7
8
var person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function(){
alert(this.name);
}
};

2,使用 new 运算符

new 运算符可以创建用户自定义的对象类型具有构造函数的内置对象类型的实例。

两种情况

  1. 函数 return是对象,则新对象接收该对象。
  2. 函数没有 return 或者 return不是对象,则把该函数的 this(上下文)指定给新对象。
1
2
3
4
5
6
7
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
alert(this.name);
};

对象的属性

1,内部属性和 [[prototype]]

通常情况下,内部属性不能直接访问,用两对方括号表示,例如:[[prototype]]

每一个 JavaScript 对象都有一个名为 [[prototype]] 的内部属性,用来存放该对象指向的原型对象,称为原型链。

1)检查原型链:

Object.prototype.isPrototypeOf() 方法

语法: prototype.isPrototypeOf(object)

检测 prototype 对象 是否在 object 对象的原型链上。

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
this.name = name;
}

var person1 = new Person("bell");
var person2 = Object.create(person1);

person1.isPrototypeOf(person2); // true
person2.isPrototypeOf(person1); // false
Person.isPrototypeOf(person1); // false
Person.prototype.isPrototypeOf(person1); // true
Person.prototype.isPrototypeOf(person2); // true

另一个检查原型链的办法:

instanceof 运算符

语法: object instanceof constructor

用来检查 constructor.prototype 是否存在于 object 的原型链上。

注意:

isPrototypeOfinstanceof 云算法的作用完全不同。
在表达式 object instanceof constructor 中,检测的是 constructor.prototype 是否在 object 的原型链上,而不是检测 constructor 本身。

2)获取原型:

Object.getPrototypeOf() 方法

语法: Object.getPrototypeOf(object)

获取对象的 [[Prototype]] 内部属性。

1
Object.getPrototypeOf(person) === Object.prototype  // true

ES5 中,如果参数不是一个对象类型,将抛出一个 TypeError 异常。
ES6 中,参数被强制转换为 Object

3)设置原型:

__proto__ 属性 和 Object.setPrototypeOf() 方法

ES6 之前,可以通过 __proto__ 属性设置,ES6 开始,可以用 Object.setPrototypeOf() 方法来设置。

4)应用:

添加原型链

通过 Object.getPrototypeOf()Object.setPrototypeOf() 的组合,可以给一个新的原型对象添加完整的原型链:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
*** Object.appendChain(@object, @prototype)
*
* Appends the first non-native prototype of a chain to a new prototype.
* 把新的原型对象设置为首个非原生原型对象的原型。
* Returns @object (if it was a primitive value it will transformed into an object).
*
*** Object.appendChain(@object [, "@arg_name_1", "@arg_name_2", "@arg_name_3", "..."], "@function_body")
*** Object.appendChain(@object [, "@arg_name_1, @arg_name_2, @arg_name_3, ..."], "@function_body")
*
* Appends the first non-native prototype of a chain to the native Function.prototype object, then appends a
* new Function(["@arg"(s)], "@function_body") to that chain.
* Returns the function.
*
**/

Object.appendChain = function(oChain, oProto) {
if (arguments.length < 2) {
throw new TypeError('Object.appendChain - Not enough arguments');
}
if (typeof oProto === 'number' || typeof oProto === 'boolean') {
throw new TypeError('second argument to Object.appendChain must be an object or a string');
}

// new oChain.constructor(oChain);把基本类型转换为对象类型
var oNewProto = oProto,
oReturn = o2nd = oLast = oChain instanceof this ? oChain : new oChain.constructor(oChain);

/*
* 从原型链底层向上推, o2nd存当前对象,o1st存当前对象的原型对象
* 直到遇到当前对象的原型为 Object.prototype 或 Function.prototype 为止
*/
for (var o1st = this.getPrototypeOf(o2nd);
o1st !== Object.prototype && o1st !== Function.prototype;
o1st = this.getPrototypeOf(o2nd)
) {
o2nd = o1st;
}

/*
* 如果第二个参数为字符串:
* 1,把 原生-函数原型对象 赋值为 新原型对象
* 2,把 参数字符串 构造成 新函数对象
* 3,把 参数原对象 设置为 新函数对象的原型对象
*/
if (oProto.constructor === String) {
oNewProto = Function.prototype;
oReturn = Function.apply(null, Array.prototype.slice.call(arguments, 1));
this.setPrototypeOf(oReturn, oLast);
}
// 把当前对象的原型对象 设置为 新原型对象
this.setPrototypeOf(o2nd, oNewProto);
return oReturn;
}

2,Object.prototype.constructor 属性

函数在创建时会带有 prototype 属性,称为原型对象
该对象有一个不可枚举且可修改的 constructor 属性,其值默认指向该函数。

对象自身并没有 constructor 属性,该属性都继承自其原型对象。

知乎:JavaScript 中对象的 constructor 属性的作用是什么?

反之,如果需要隐藏构造函数的原型对象,可修改实例的 constructor 属性,
所以,依赖一个对象的 constructor 属性并不安全。

3,属性

1)获取属性

获取自身属性

Object.keys() 方法

返回一个由给定对象的所有可枚举自身属性的属性名组成的数组。

Object.getOwnPropertyNames() 方法

返回一个由指定对象的所有自身属性(包括不可枚举)的属性名组成的数组。

获取所有属性

for…in 语句

遍历对象上可枚举的属性(包括自身属性继承属性)。

2)设置属性

直接赋值

1
person.name = 'bell';

通过方法赋值

Object.defineProperty() 方法

语法: Object.defineProperty(obj, prop, descriptor)

通过定义或修改 属性描述对象 来给属性赋值,并返回该对象。

Object.defineProperties() 方法

语法: Object.defineProperties(obj, props)

在一个对象上添加或修改一个或者多个自身属性,并返回该对象。

3)删除属性

delete 操作符

用来删除一个对象的自身属性。

在严格模式下,如果属性是一个不可配置(non-configurable)属性,删除时会抛出异常。
非严格模式下,返回 false
其他情况都返回 true(如果删除的属性不存在也会返回 true)。

delete 操作符可以删除一个隐式声明的全局变量。

一些对象的属性不能被deleteECMA 262 规范中把这些属性标记为 DontDelete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var y = 2;
window.y // 2
Object.getOwnPropertyDescriptor(window, "y");
// Object {value: 2, writable: true, enumerable: true, configurable: false}
// 显式声明的全局变量不能被删除,因为该属性不可配置(non-configurable)
delete y; // false

//内置对象的内置属性不能被删除
delete Math.PI; // false

function f() {
var z = 3;
// 局部变量不能删除
delete z; // false
}

当使用 delete 删除一个数组元素时,数组的 length 属性并不会变小,被删除的元素已经不属于该数组。

1
2
3
4
5
var trees = ["redwood","bay","cedar","oak","maple"];
delete trees[3];
if (3 in trees) {
// 这里不会被执行
}

4)检查属性

Object.prototype.hasOwnProperty() 方法

使用 hasOwnProperty() 方法来判断某个对象是否含有指定的自身属性,
in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。

注意:

hasOwnProperty 方法有可能被遮蔽

如果一个对象拥有自己的 hasOwnProperty 方法, 则原型链上的同名方法会被遮蔽(shadowed):

1
2
3
4
5
6
7
8
9
10
11
12
var foo = {
hasOwnProperty: function() {
return false;
},
bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); // 始终返回 false

// 如果担心这种情况,可以直接使用原型链上真正的 hasOwnProperty 方法
({}).hasOwnProperty.call(foo, 'bar'); // true
Object.prototype.hasOwnProperty.call(foo, 'bar'); // true

in 运算符

检查指定的属性是否存在于指定的对象中,包括从原型链上继承的属性。

只有 delete 的属性才会返回 false,值为 undefined 的属性会返回 true

注意:

数组是一个特殊的对象,在对数组做属性检查时,其实检查的是数组的下标。

4,属性赋值的流程

两种情况

1)对象有该自身属性

1,是否是访问器属性(是否有 [[set]] 函数),是则调用。

2,是否可写(writable:false),是则静默失败或抛出 TypeError 异常。

3,如果都不是,则把该值设置为属性的值。

2)对象没有该自身属性

1,如果原型链上也没有该属性,则该属性会被添加到对象上成为自身属性

2,如果原型链上存在该属性,则会有三种情况

  1. 如果原型链上的属性为数据属性,且 writable:true,则该属性会被添加到对象上,称为屏蔽属性

  2. 如果原型链上的属性为数据属性,且 writable:false,则创建屏蔽属性静默失败,或者抛出 TypeError 异常。

  3. 如果原型链上的属性为访问器属性,则会调用原型链上的 [[set]] 函数,如果函数中存在通过 this 存放的过渡属性,则会在对象上也创建一个过渡属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var obj1 = {};
    Object.defineProperty(obj1, 'age', {
    set: function (val) {
    this._age_ = val;
    },
    get: function () {
    return this._age_ || 10;
    }
    });

    var obj2 = Object.create(obj1);

    obj1.age = 20;
    obj2.age = 30;

    obj1.age // 20
    obj2.age // 30
    obj2 // Object {_age_: 30}

如果想在第二种和第三种情况下也实现屏蔽属性,则需要使用 Object.defineProperty() 方法。

5,属性描述对象

Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自身属性的属性描述对象。

(自身属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性。)

1)数据属性

[[value]]:属性的值。

[[writable]]:属性的值是否可修改。

[[enumerable]]:属性是否可枚举。

[[configurable]]:属性是否可配置,
设置为 false 后,该属性不可使用 delete 删除,不可使用 Object.defineProperty() 等方法修改。

注意:

当修改 configurable:false 后,属性只接受 writable:false 的修改,

其他修改都会抛出 TypeError 的错误,或静默失败。

1
2
3
4
5
6
Object.defineProperty(person, 'name', {configurable:false});
// Object {value: "bell", writable: true, enumerable: true, configurable: false}
Object.defineProperty(person, 'name', {writable:false});
// Object {value: "bell", writable: false, enumerable: true, configurable: false}
Object.defineProperty(person, 'name', {enumerable:false});
// TypeError

2)访问器属性

当给一个属性定义 [[get]][[set]] 时,该属性会被定义为访问器属性

访问器属性会忽略 valuewritable 特性。

[[enumerable]]:同数据属性。

[[configurable]]:同数据属性。

[[get]]:读取属性值的函数,只有该函数则意味着不能写入属性值。

[[set]]:写入属性值的函数,只有该函数则意味着不能读取属性值。

6,默认的属性描述对象

1)直接赋值

1
2
3
person.name = 'bell';
Object.getOwnPropertyDescriptor(person,'name');
// Object {value: "bell", writable: true, enumerable: true, configurable: true}

2)通过 Object.defineProperty() 等方法赋值

1
2
3
Object.defineProperty(person, 'age', {});
Object.getOwnPropertyDescriptor(person,'age');
// Object {value: undefined, writable: false, enumerable: false, configurable: false}

7,不变性的应用

1)对象常量

使用 writable:falseconfigurable:false 创建一个常量属性

1
2
3
4
5
6
var myObject = {};
Object.defineProperty(myObject, 'FAVORITE_NUMBER', {
value: 2,
writable: false,
configurable: false
});

2)禁止扩展

使用 Object.preventExtensions() 方法让一个对象变的不可扩展,并且返回原对象。

Object.preventExtensions() 只能阻止一个对象不能再添加新的自身属性,仍然可以为该对象的原型添加属性。

使用 Object.isExtensible() 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。

密封冻结对象均为不可扩展。

3)密封

使用 Object.seal() 方法让一个对象密封,并返回被密封后的对象。

密封一个对象会让这个对象变的不能添加新属性,且所有已有自身属性会变的不可配置,
但属性的值仍然可以修改,且不会影响从原型链上继承的属性。

使用 Object.isSealed() 方法判断一个对象是否是密封的。

密封对象是指那些不可扩展的,且所有自身属性都不可配置的对象。

4)冻结

使用 Object.freeze() 方法可以冻结一个对象。

冻结对象的所有自身属性都不可能以任何方式被修改,包括数据属性和访问器属性。
如果一个属性的值是个对象,则这个对象中的属性是可以修改的,除非它本身也是个冻结对象。

使用 Object.isFrozen() 方法判断一个对象是否被冻结的。