理解对象
- 每个对象都是基于一个
引用类型
创建的 - JS 中的所有对象都是继承自 Object 对象
对象特性
- 结构类似「字典」 :对象的属性类似键 / 值对;属性的名称为字符串,属性的值为任意类型。
- 原型继承:Js 的对象可继承原型的属性。
- 动态结构:可动态新增、删除对象的属性。
- 引用类型:js 中的对象为引用类型。a 为一个对象,b=a,修改 b 也会造成 a 的修改。
创建对象的方式
有 3 种定方形式:对象字面量、new 构造函数和 Object.create()
对象字面量成为创建对象的首选模式
每一种创建方式继承的原型对象
都不同
Object.create() 允许为创建的对象选择原型对象,不需要定义一个构造函数
对象字面量:Object.prototype
var obj = {name:'xusir'} console.log(obj.constructor.prototype); // Object() 对象字面量的原型为Object console.log(obj.constructor.prototype === Object.prototype); // true
new 构造函数:构造函数的 prototype 属性
// 创建构造函数 function People(name) { this.name; } // 创建对象 var p = new People('Tom'); console.log(p.constructor.prototype); // People{} 原型为构造函数的 prototype console.log(p.constructor.prototype === People.prototype); // true
Object.create(prototype, propertyDescriptor):默认原型为 参数 prototype;若 prototype 为null,对象的原型为 undefined
// 1.建立一个原型为null的对象 var o = Object.create(null, { name: { value: 'tom' } }); console.log(o.constructor); // undefined // 2.创建一个原型为Array的对象 var array = Object.create(Array.prototype, {}); console.log(array.constructor); // function Array 构造函数 console.log(array.constructor.prototype); // [] 原型对象为数组
对象实例的属性和方法
当我们用 obj.xxx 访问一个对象的属性时,JavaScript 引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到 Object.prototype 对象,最后,如果还没有找到,就只能返回 undefined。
var arr = [1, 2, 3];
//其原型链是
arr 》 Array.prototype 》 Object.prototype 》 null
⚠️ 如果原型链很长,那么访问一个对象的属性就会因为花更多的时间查找而变得更慢,因此要注意不要把原型链搞得太长
对象属性是无序的
一个对象是一个 Object 类型的实例。它是由一些未排序的元素组成的集合,其中包含了原始变量,对象,和函数。一个对象的属性所对应的函数被称为方法
对象的输出是无序的
,但是数组却是有序的,所以为了保证顺序,搞成数组再输出
怎么解决呢❓
1. constructor 属性 constructor 属性是保存当前对象的构造函数
var obj1 = new Object();
obj1.id = "obj1";
var obj2 = {
"id": "obj2"
};
console.log(obj1.constructor);//function Object(){}
console.log(obj2.constructor);//function Object(){}
2. hasOwnProperty(propertyName) 方法 判断该属性是否在当前对象实例中,而不是在对象的原型链中
var arr = [];
console.log(arr.hasOwnProperty("length"));//true
console.log(arr.hasOwnProperty("hasOwnProperty"));//false
3. isPrototypeOf(Object) 方法 isPrototype 方法接收一个对象,用来判断当前对象是否在传入的参数对象的原型链上
function MyObject() {}
var obj = new MyObject();
console.log(Object.prototype.isPrototypeOf(obj)); // true
MyObject 是继承自 Object 对象的,而在 JS 中,继承是通过 prototype 来实现的,所以 Object 的 prototype 必定在 MyObject 对象实例的原型链上
4. propertyIsEnumerable(prototypeName) 方法 判断给定的属性是否可以被 for..in 语句给枚举出来
console.log(obj.propertyIsEnumerable("constructor")); // false
5. toString() 方法 返回对象的字符串表示。
var obj = {};
console.log(obj.toString());//[object Object]
6. valueOf() 方法 返回对象的原始值,可能是字符串、数值或 bool 值等,看具体的对象。
var obj = {
name: "obj"
};
console.log(obj.valueOf());//Object {name: "obj"}
var arr = [1];
console.log(arr.valueOf());//[1]
var date = new Date();
console.log(date.valueOf());//1456638436303
属性类型
ECMAScript中有 2 种属性:数据属性
和访问器属性
。
1. 数据属性
数据属性我们可以理解为我们平时定义对象时赋予的属性,它可以进行读和写。
但 ES5 中定义了一些特性,这些特性是用来描述属性的各种特征,特性是内部值,不能直接访问到。特性通过用两对方括号表示,比如[[Enumerable]]。属性的特性会有一些默认值,要修改特性的默认值,必须使用 ES5 定义的新方法 Object.defineProperty 方法来修改。
4 个描述其行为的特性
- [[Configurable]]:能否修改属性(默认值 true)
- [[Enumerable]]:能否能被 for...in 循环或 Object.keys 方法遍历得到(默认值 true)
- [[Writeable]]:能否修改属性的值(默认值 true)
- [[Value]]:包含这个属性的数据值(默认值 undefined)
直接在对象上定义的属性,它们的 [[Configurable]]、[[Enumerable]] 和 [[Writable]]特性都被设置为 true
,而 [[Value]] 特性被设置为指定的值。例如:
var obj = {
name:"bob"
}
Q:如何修改属性默认的特性呢❓
- 必须使用 ES5 的 Object.defineProperty() 方法
调用 Object.defineProperty() 方法时,如果不指定,configurable、enumerable 和 writable特性的默认值都是
false
var obj = {} Object.defineProperty(obj, 'name', {value:'初始值'}); obj.name = 'bob'; console.log(obj.name); // 初始值 // 等价于下面 //////////////// Object.defineProperty(obj, 'name', { value: '初始值', enumerable: false, configurable: false, writable: false, });
Q:configurable 与 writable 的区别❓
- writable:false
该属性值不能被修改
- configurable:false
该属性不能被删除,以及
除 writable
特性外的其他特性是否可以被修改。 - writable:false && configurable:false
该属性不能被删除,以及
所有
特性是否可以被修改
2. 访问器属性
访问器属性有点类似于 C# 中的属性,和数据属性的区别在于,它没有数据属性的 [[Writable]] 和 [[Value]] 两个特性,而是拥有一对 getter 和 setter 函数
[[Get]]:读取属性时调用的函数,默认是undefined [[Set]]:设置属性时调用的函数,默认是undefined
getter 和 setter 是一个很有用的东西,假设有两个属性,其中第二个属性值会随着第一个属性值的变化而变化
var person = {
age: 10
}
Object.defineProperty(person, "type", {
get: function () {
if (person.age > 17) {
return "成人";
}
return "小孩";
}
})
console.log(person.type);//小孩
person.age = 18;
console.log(person.type);//成人
通过修改 age 的值,type 的值也会相应的修改,这样我们就不用再手动的去修改 type 的值了
下面这种方式也是可以实现同样的效果:
var person = {
_age: 10,
type: "小孩"
}
Object.defineProperty(person, "age", {
get: function () {
return this._age;
},
set: function (newValue) {
this._age = newValue;
this.type = newValue > 17 ? "成人" : "小孩";
}
})
console.log(person.type);
person.age = 18;
console.log(person.type);
⚠️ 关于访问器属性,有几点要注意:
- 严格模式下,必须同时设置 get 和 set
- 非严格模式下,可以只设置其中一个,如果只设置 get,则属性是只读的,如果只设置 set,属性则无法读取
- Object.defineProperty 是 ES5 中的新方法,IE9(IE8 部分实现,只有 dom 对象才支持)以下浏览器不支持,一些旧的浏览器可以通过非标准方法 defineGetter() 和 defineSetter() 来设置,这里就不说明了,有兴趣的同学可以查找相关资料。
3. 特性操作的相关方法 ES5 提供了一些读取或操作属性特性的方法,前面用到的 Object.defineProperty 就是其中之一。以下是一些比较常用的方法:
3.1 Object.defineProperty 定义一个对象的属性,这个方法前面我们已经用到多次
3.2 Object.defineProperties 和 defineProperty 类似,不同的是它可以用来同时定义多个属性
var obj = {};
Object.defineProperty(obj, {
"name": {
value: "name",
configurable: true,
writable: true,
enumerable: true
},
"age": {
value: 20
}
});
3.3 Object.getOwnPropertyDescriptor 一个读取特性值的方法,该方法接收对象及其属性名作为两个参数,返回一个对象,根据属性类型的不同,返回对象会包含不同的值。
var person = {
_age: 10,
type: "小孩"
}
Object.defineProperty(person, "age", {
get: function () {
return this._age;
},
set: function (newValue) {
this._age = newValue;
this.type = newValue > 17 ? "成人" : "小孩";
}
})
console.log(Object.getOwnPropertyDescriptor(person, "type"));//Object {value: "成人", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(person, "age")); //Object {enumerable: false, configurable: false, get: function(),set: function ()}
序列化
通过调用 JSON 方法,可以将对象序列化成字符串 / 将 Json 字符串反序列化成对象
1. JSON.stringify(object):把对象转换成一个字符串
var o = {
x: 1,
y: 2
}
JSON.stringify(o); // {"x":1,"y":2} 返回一个字符串
函数也是一个对象,它的原型链是
function foo() {
return 0;
}
foo 》 Function.prototype 》 Object.prototype 》 null
2. JSON.parse(jsonStr) :将一个 Json 字符串转换为对象
var str = '{ "x":1,"y":2 }'; // 字符串的属性名要用引号框起来
var o = JSON.parse(str);
console.log(o.x); // 1 输出对象属性 x 的值
存在性
myObject.a 的属性访问返回值可能是 undefined,但是这个值有可能是属性中存储的 undefined,也可能是因为属性不存在所以返回 undefined。那么如何区分这 2 种情况呢?
我们可以在不访问属性值的情况下判断对象中是否存在这个属性:
var myObject = {
a:2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false
⚠️ 看起来 in 操作符可以检查容器内是否有某个值,但是它实际上检查的是某个属性名是否存在
。对于数组来说这个区别非常重要,4 in [2, 4, 6] 的结果并不是你期待的 True,因为 [2, 4, 6] 这个数组中包含的属性名是 0、1、2,没有 4。
利用 Object.freeze() 提升性能
Object.freeze() 是 ES5 新增的特性,可以冻结一个对象,防止对象被修改。
如果你有一个巨大的数组或 Object,并且确信数据不会修改,使用 Object.freeze() 可以让性能大幅提升。在我的实际开发中,这种提升大约有 5~10 倍,倍数随着数据量递增。
vue 1.0.18+ 对其提供了支持,对于 data 或vuex 里使用 freeze 冻结了的对象,vue 不会做 getter 和 setter 的转换
扩展、密封及冻结 3 大特性
1. 扩展特性
如果一个对象可以添加新的属性,则这个对象是可扩展
的。
让这个对象变的不可扩展,也就是不能再有新的属性
Object.isExtensible(obj)
判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)
Object.preventExtensions(obj)
让一个对象变的不可扩展(永远不能再添加新的属性)
2. 密封特性
密封对象是指那些不可扩展 的,且所有自身属性都不可配置的(non-configurable)对象。
或则说 密封对象是指那些不能添加新的属性,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性,但可能可以修改已有属性的值的对象
Object.isSealed(obj)
判断一个对象是否是密封的(sealed)
Object.seal(obj)
让一个对象密封,并返回被密封后的对象
3. 冻结特性
一个对象是冻结的(frozen)是指它不可扩展,所有属性都是不可配置的(non-configurable),且所有数据属性(data properties)都是不可写的(non-writable)。
或则说 冻结对象是指那些不能添加新的属性,不能修改已有属性的值,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性的对象。也就是说,这个对象永远是不可变的。
Object.isFrozen(obj)
判断一个对象是否被冻结(frozen)
Object.freeze(obj)
冻结一个对象
源码学习
Q & A
Q:宿主对象和原生对象的区别❓
原生对象 ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript实现提供的对象”。 “本地对象”包含哪些内容:Object、Function、Array、String、Boolean、Number、Date、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError。 由此可以看出,简单来说,本地对象就是 ECMA-262 定义的类(引用类型)。
内置对象 ECMA-262 把内置对象(built-in object)定义为“由 ECMAScript实现提供的、独立于宿主环境的所有对象,在 ECMAScript程序开始执行时出现”。这意味着开发者不必明确实例化内置对象,它已被实例化了。 同样是“独立于宿主环境”。根据定义我们似乎很难分清“内置对象”与“本地对象”的区别。而ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每个内置对象都是本地对象)。如此就可以理解了。内置对象是本地对象的一种。
宿主对象 何为“宿主对象”?主要在这个“宿主”的概念上,ECMAScript中的“宿主”当然就是我们网页的运行环境,即“操作系统”和“浏览器”。 所有非本地对象都是宿主对象(host object),即由 ECMAScript实现的宿主环境提供的对象。所有的BOM和DOM都是宿主对象。因为其对于不同的“宿主”环境所展示的内容不同。其实说白了就是,ECMAScript官方未定义的对象都属于宿主对象,因为其未定义的对象大多数是自己通过ECMAScript程序创建的对象。
Q:为什么 typeof(null) 是 "object"?
从逻辑角度来看,null值表示一个空对象指针,而这正是使用typeof操作符检测null值时会返回“object”的原因 --《JavaScript高级程序设计 第3版 》P100
null 不是一个空引用, 而是一个原始值, 参考ECMAScript5.1中文版 4.3.11节; 它只是期望此处将引用一个对象, 注意是"期望", 参考 null - JavaScript.
typeof null结果是object, 这是个历史遗留bug, 参考 typeof - JavaScript
在ECMA6中, 曾经有提案为历史平凡, 将type null的值纠正为null, 但最后提案被拒了. 理由是历史遗留代码太多, 不想得罪人, 不如继续将错就错当和事老, 参考 harmony:typeof_null [ES Wiki]
🌱 学习参考