对象----《你不知道的JS》

最近在拜读《你不知道的js》,而此篇是对于《你不知道的js》中对象部分的笔记整理,但愿能有效的梳理,而且深刻理解对象算法

1、语法

对象两种定义形式:声明(文字)形式、构造形式json

声明(文字)形式

var myObj = {
   key: value,
   ...
}
复制代码

构造形式

var myObj = new Object();
myObj.key = value;
复制代码

构造形式与文字形式生成的对象同样数组

区别:文字声明中能够添加多个键/值对,构造形式中必须逐个添加属性安全

2、类型

在JavaScript中一共有6中主要类型:bash

  • string
  • number
  • boolean
  • null
  • undefined
  • object

注意:简单基本类型(string、boolean、number、null、undefined)自己不是对象。null有时会被当作一种对象类型,typeof null返回‘object’。实际上,null是基本类型函数

内置对象

内置对象:对象子类型ui

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

3、内容

对象的内容是由一些存储在特定命名位置的(任意类型的)值组成,咱们称为属性。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(...),但可能会在无心中冻结其余(共享)对象

七、[[Get]]

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]]被触发时,实际行为取决于许多因素,包括对象中是否已经存在这个属性(最重要的因素)

若是已经存在这个属性,[[Put]]算法大体会检查下面这些内容: 1)属性是不是访问描述符?若是是而且存在setter就调用setter 2)属性的数据描述符中writable是不是false?若是是,在非严格模式下静默注册失败,在严格模式下抛出TypeError异常

九、Getter和Setter

在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(..)只会查找对象直接包含的属性

4、遍历

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
复制代码