最近在拜读《你不知道的js》,而此篇是对于《你不知道的js》中对象部分的笔记整理,但愿能有效的梳理,而且深刻理解对象算法
对象两种定义形式:声明(文字)形式、构造形式json
var myObj = {
key: value,
...
}
复制代码
var myObj = new Object();
myObj.key = value;
复制代码
构造形式与文字形式生成的对象同样数组
区别:文字声明中能够添加多个键/值对,构造形式中必须逐个添加属性安全
在JavaScript中一共有6中主要类型:bash
注意:简单基本类型(string、boolean、number、null、undefined)自己不是对象。null有时会被当作一种对象类型,typeof null返回‘object’。实际上,null是基本类型函数
内置对象:对象子类型ui
对象的内容是由一些存储在特定命名位置的(任意类型的)值组成,咱们称为属性。this
var myObject = {
a: 2
};
myObject.a;// 2
myObject['a']; // 2
复制代码
若要访问myObject中a位置上的值,需使用.操做符或[]操做符。.a语法称为属性访问(最多见的方式),['a']语法称为键访问spa
区别:.操做符要求属性名知足标识符的命名规范,而[".."]语法能够接受任意UTF-8/Unicode字符串做为属性名,如名称为“Super-Fun”的属性,就必须使用["Super-Fun"]语法访问prototype
因为['..']语法使用字符串来访问属性,因此能够在程序中构造这个字符串,如:
var myObject = {
a: 2
};
var idx;
if(wantA) {
idx = "a";
}
console.log(myObject[idx]); // 2
复制代码
在对象中,属性名永远都是字符串。若使用string(字面量)之外的其余值做为属性名,那它首先会被转换为一个字符串。
var myObject = {};
myObject[true] = 'foo';
myObject[3] = "bar";
myObject[myObject]="baz";
myObject["true"]; // 'foo'
myObject['3']; //"bar"
myObject["object object"]; // "baz"
复制代码
ES6增长了可计算属性名:
var prefix = "foo";
var myObject = {
[prefix + "bar"]: "hello",
[prefix + "baz"]: "world"
};
myObject["foobar"];// hello
myObject["foobaz"];// world
复制代码
在其余语言中,属于对象(也称为“类”)的函数一般被称为“方法”,有时属性方位也称为“方法访问”。
不管返回值是什么类型,每次访问对象的属性就是属性访问。若属性访问返回一个函数,那它也并非一个“方法”。属性访问返回的函数和其余函数没有任何区别
function foo() {
console.log("foo");
}
var someFoo = foo; // 对foo变量的引用
var myObject = {
someFoo: foo,
}
foo; // function foo() {}
someFoo; // function foo() {}
myObject.someFoo; // function foo() {}
复制代码
someFoo与myObject.someFoo只是对于同一个函数的不一样引用,并不能说明这个函数是特别的或“属于”某个对象
数组也支持[]访问形式,经过数值下标,即存储位置(索引)访问,是非负整数,如:0,42:
var myArray = ["foo", 42, "bar"];
myArray.length; // 3
myArray[0]; // "foo"
myArray[2]; // "bar"
复制代码
数组也是对象,也能够给数组添加属性:
var myArray = ["foo", 42, "bar"];
myArray.baz = "baz";
myArray.length; // 3
myArray.baz; // "baz"
复制代码
虽然添加了命名属性,数组的length值并未发生变化。 若你试图向数组添加一个属性,但属性名“看起来”像数字,那它会变成一个数值下标:
var myArray = ["foo", 42, "bar"];
myArray["3"] = "baz";
myArray.length; // 4
myArray[3] = "baz";
复制代码
function anotherFunction() {/*..*/}
var anotherObject = {
c: true
}
var anotherArray = [];
var myObject = {
a: 2,
b: anotherObject, // 引用,不是复本
c: anotherArray, // 另外一个引用
d: anotherFunction
}
anotherArray.push(anotherObject, myObject);
复制代码
如何准确地表示myObject的复制呢?
首先判断它是浅复制仍是深复制。
1)浅复制 复制出的新对象中a的值会复制就对象中a的值,即2,但新对象中b、c、d三个属性其实只是三个引用,和就对象中b、c、d引用的对象同样 2)深复制 除了复制myObject之外还会复制anotherObject和anotherArray
问题:anotherArray引用了anotherObject和myObject,因此又须要复制,myObject,这样会因为循环引用致使死循环
如何解决?
1)对于json安全的对象来讲:
var newObj = JSON.parse(JSON.stringify(someObj));
复制代码
这种方法须要保证对象是json安全的,因此只适用于部分状况。
2)ES6定义了Object.assign(..)方法来实现浅复制。Object.assign(..)方法的第一个参数是目标对象,以后还能够跟一个或多个源对象。它会遍历一个或多个源对象的全部可枚举的自有键并把它们复制(使用 = 操做符赋值)到目标对象:
var newObj = Object.assign({}, myObject);
newObj.a;// 2
newObj.b === anotherObject; // true
newObj.c === anotherArray; // true
newObj.d === anotherFunction; // true
复制代码
注:因为Object.assign(...)使用 = 操做符来赋值,因此源对象属性的一些特性不会被复制到目标对象。
在ES5以前,JavaScript语言自己并无提供可直接检测属性特性的方法,如判断属性是不是只读。从ES5开始,全部的属性都具有了属性描述符。
var myObject = {
a: 2
}
Object.getOwnPropertyDescriptor(myObject, "a");
// {
// value: 2,
// writable: true, // 可写
// enumerable: true, // 可枚举
// configurable: true // 可配置
// }
复制代码
在建立普通属性时属性描述符会使用默认值,可以使用Object.defineProperty(...)来添加一个新属性或修改一个已有属性,并对特性进行设置。
var myObject = {};
Object.defineProperty(myObject, "a", {
value: 2,
writable: true,
configurable: true,
enumerable: true
});
myObject.a; // 2
复制代码
1)writable 决定是否能够修改属性的值
var myObject = {};
Object.defineProperty(myObject, "a", {
value: 2,
writable: false, // 不可写
configurable: true,
enumerable: true
});
myObject.a = 3;
myObject.a; // 2
复制代码
在严格模式下会报错
"use strict"
var myObject = {};
Object.defineProperty(myObject, "a", {
value: 2,
writable: false, // 不可写
configurable: true,
enumerable: true
});
myObject.a = 3; // TypeError
复制代码
2)Configurable 只要属性可配置,就能够用defineProperty(...)方法修改属性描述符:
var myObject = {
a: 2
};
myObject.a = 3;
myObject.a; // 3
Object.defineProperty(myObject, "a", {
value: 4,
writable: true,
configurable: false, // 不可配置
enumerable: true
});
myObject.a; // 4
myObject.a = 5;
myObject.a; // 5
Object.defineProperty(myObject, "a", {
value: 6,
writable: true,
configurable: true,
enumerable: true
}); // TypeError
复制代码
不管是否处于严格模式,尝试修改一个不可配置的属性描述符都会出错。
注:把Configurable 修改为false是单向操做,没法撤销; 即使属性是configurable:false,咱们仍是能够把writable的状态由true改成false,但没法由false改成true。
除了没法修改,configurable: false还会禁止删除这个属性:
var myObject = {
a: 2
};
myObject.a = 2;
delete myObject.a;
myObject.a; // undefined
Object.defineProperty(myObject, "a", {
value: 2,
writable: true,
configurable: false, // 不可配置
enumerable: true
});
myObject.a; // 2
delete myObject.a;
myObject.a; // 2
复制代码
在本例中,delete只用来直接删除对象的(可删除)属性。若对象的某个属性是某个对象/函数的最后一个引用者,对这个属性执行delete操做后,这个对象/函数就能够被垃圾回收
3)enumerable 控制属性是否出如今对象的属性枚举类中,如for...in循环,若把enumerable设置为false,属性就不会出如今枚举中,虽然仍能够正常访问它
ES5中全部方法建立的都是浅不变性,即它们只会影响目标对象和它的直接属性。若是目标对象引用了其余对象(数组、对象、函数等),其余对象的内容不受影响,还是可变的:
myImmutableObject.foo; // [1,2,3]
myImmutableObject.foo.push(4);
myImmutableObject.foo; // [1,2,3,4]
复制代码
1)对象常量
结合writable:false和configurable:false就能够建立一个真正的常量属性(不可修改、从新定义或者删除)
var myObject = {};
Object.defineProperty(myObject, "FAVORITE_NUMBER", {
value: 42,
writable: false,
configurable: false, // 不可配置
});
复制代码
2)禁止扩展
Object.preventExtensions(...):禁止一个对象添加新属性而且保留已有属性
var myObject = {
a: 2
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
复制代码
非严格模式下,建立属性b会静默失败,在严格模式下,将抛出TypeError
3)密封
Object.seal(...)会建立一个“密封”对象,这个方法实际上会在一个现有对象上调用Object.preventExtensions(...)并把全部现有属性标记为configurable:false。因此密封后不只不能添加新属性,也不能从新配置或删除任何现有属性(虽然能够修改属性的值)
4)冻结 Object.freeze(...)会建立一个冻结对象,实际上会在一个现有对象上调用Object.seal(...)并把全部“数据访问”属性标记为writable:false,这样就没法修改值
这个方法可应用在对象上的级别最高的不可变性,它会禁止对象自己及其任意直接属性的修改,这个对象的引用的其余对象是不受影响的
深度冻结方法:首先在这个对象上调用Object.freeze(...),而后遍历它引用的全部对象并在这些对象上调用Object.freeze(...),但可能会在无心中冻结其余(共享)对象
var myObject = {
a: 2
}
myObject.a; // 2
复制代码
myObject.a在myObject上实际是实现了[[Get]]操做。对象默认的内置[[Get]]操做首先在对象中查找是否有名称相同的属性,若是找到就会返回这个属性的值,若没找到,按照[[Get]]实验法的定义会执行另一种很是重要的行为,即遍历可能存在的[[Prototype]]链,也就是原型链。
若是不管如何都没有找到名称相同的属性,那[[Get]]操做会返回undefined
var myObject = {
a: 2
}
myObject.b; // undefined
复制代码
注:这种方法和访问变量时是不同的,若你引用了一个当前词法做用域中不存在的变量,并不会像对象属性同样返回undefined,而是会抛出ReferenceError异常:
var myObject = {
a: undefined
}
myObject.a; // undefined
myObject.b; // undefined 因为根据返回值没法判断出到底变量的值为undefined仍是变量不存在,因此[[Get]]操做返回了undefined
复制代码
[[Put]]被触发时,实际行为取决于许多因素,包括对象中是否已经存在这个属性(最重要的因素)
若是已经存在这个属性,[[Put]]算法大体会检查下面这些内容: 1)属性是不是访问描述符?若是是而且存在setter就调用setter 2)属性的数据描述符中writable是不是false?若是是,在非严格模式下静默注册失败,在严格模式下抛出TypeError异常
在ES5中可以使用getter和setter部分改写默认操做,但只能应用在单个属性上,没法应用在整个对象上。getter是一个隐藏函数,会在获取属性值时调用。setter也是一个隐藏函数,会在设置属性时调用。
当你给一个属性定义getter和setter或者二者都有时,这个属性会被定义为“访问描述符”,对于访问描述符来讲,js会忽略它们的value和writable特性,取而代之的关心set和get(还有configurable和enumerable)
var myObject = {
get a() {
return 2;
}
}
Object.defineProperty(
myObject, // 目标对象
"b", // 属性名
{
get: function() {
return this.a * 2
},
enumerable: true
}
);
myOject.a; // 2
myObject.b; // 4
复制代码
两种方式都会在对象中建立一个不包含值的属性,对于这个属性的访问会自动调用一个隐藏函数,它的返回值会被当作属性访问的返回值:
var myObject = {
get a() {
return 2;
}
}
myOject.a = 3;
myObject.a; // 2
复制代码
因为咱们只定义了a的getter,因此对a的值进行设置时set操做会忽略赋值操做,不会抛出错误。
一般来讲getter和setter是成对出现的
var myObject = {
get a() {
return this._a_;
}
set a(val) {
this._a_ = val * 2;
}
}
myOject.a = 2;
myObject.a; // 4
复制代码
属性访问返回值多是undefined,如何区分这是属性中存储的undefined,仍是属性不存在而返回的undefined ?
var myObject = {
a: 2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty("a"); // true
myObject.hasOwnProperty("b"); // false
复制代码
in操做符会检查属性是否在对象及其[[Prototype]]原型链中,hasOwnProperty(..)只会检查属性是否在myObject对象中,不会检查[[Prototype]]链
注:in是检查某个属性名是否存在,如:4 in [2,4,6] = false, 由于这个数组中包含的属性名为0 ,1, 2
全部的普通对象均可以经过对于 Object.prototype 的委托来访问hasOwnProperty(..),但有的对象可能没有链接到 Object.prototype (经过Object.create(null)建立),则myObject.hasOwnProperty就会失败
此时可采用 Object.prototype.hasOwnProperty.call(myObject, "a") 进行判断,它借用基础的hasOwnProperty(..)方法并把它显示绑定在myObject上
1)枚举
“可枚举”至关于“能够出如今对象属性的遍历中”
var myObject = {};
Object.defineProperty(
myObject,
"a",
// 让a像普通对象同样可枚举
{ enumerable: true, value: 2 }
);
Object.defineProperty(
myObject,
"B",
// 让b不可枚举
{ enumerable: false, value: 3 }
);
myObject.b; // 3
("b" in myObject); // true
myObject.hasOwnProperty("b"); // true
for (var k in myObject) {
console.log(k, myObject[k]);
}
// "a" 2
复制代码
for...in枚举不只会包含全部索引,还会包含全部可枚举属性,因此最好只在对象上引用for ... in 循环,若遍历数组就使用for循环。但它没法直接获取属性值,需手动获取
也可用propertyIsEnumerable(..)来区分属性是否可枚举
var myObject = {};
Object.defineProperty(
myObject,
"a",
// 让a像普通对象同样可枚举
{ enumerable: true, value: 2 }
);
Object.defineProperty(
myObject,
"B",
// 让b不可枚举
{ enumerable: false, value: 3 }
);
myObject.propertyIsEnumerable("a"); // true
myObject.propertyIsEnumerable("b"); // false
Object.keys(myObject); // ["a"]
Object.getOwnPropertyNames(myObject); // ["a", "b"]
复制代码
propertyIsEnumerable(..)会检查给定的属性名是否直接存在于对象中(而非原型链上),而且知足enumerable: true
Object.keys(..)会返回数组,包含全部可枚举属性。Object.getOwnPropertyNames(..)只会查找对象直接包含的属性
for...in用来遍历对象的可枚举属性列表,如何遍历属性值呢?
对于数值索引的数组来讲,可用for循环。另外ES5也增长了一些数组辅助迭代器:forEach(..)、every(..)、some(..),他们均可以接受一个回调函数并把它应用在数组的每一个元素上,区别就是它们对于回调函数返回值的处理方式不一样
forEach(..):遍历数组全部值并忽略回调函数的返回值
every(..):会一直运行到回调函数返回false(或“假”值)
some(..):会一直运行直到回调函数返回true(或“真”值)
遍历数组下标时采用的数字顺序,但遍历对象属性时顺序不肯定,在不一样的js引擎中可能不同
如何直接遍历值而不是数组下标?
使用for..of (ES6增长的语法)
var myArray = [1, 2, 3];
for(var v of myArray) {
console.log(v);
}
// 1
// 2
// 3
复制代码
for..of首先会向被访问对象请求一个迭代器对象,而后经过调用迭代器对象的next()方法来遍历全部返回值
数组有内置的@@iterator,也可直接应用在数组上。
var myArray = [1, 2, 3];
var it = myArray[Symbol.iterator]();
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {done: true}
复制代码
value是遍历值,done是布尔值,表示是否还有可遍历的值
普通对象中没有内置的@@iterator,因此没法自动完成for..of,但能够结合for..of循环与自定义迭代器来操做对象
var myObject = {
a: 2,
b: 3
}
Object.defineProperty(myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function() {
var o = this;
var idx = 0;
var ks = Object.keys(o);
return {
next:function() {
return {
value: o[ks[idx++]],
done: (idx > ks.length)
}
}
}
}
});
// 手动遍历
var it = myObject[Symbol.iterator]();
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: undefined, done: true}
// for .. of
for (var v of myObject) {
conosle.log(v);
}
// 2
// 3
复制代码