PatrickLh/blog

Javascript学习笔记-Object

Opened this issue · 0 comments

Object对象.png

1. 基础

1.1 基本类型

曾经听说Javascript万物皆对象,没对这句话有过更多的考虑,总觉得是对的,但是实际上并不一定妥当,对象类型是Javascript的6个基本类型之一。Javascript包含六种基本类型,分别是number, string, boolean, null, undefined, object
其中,虽然typeof null === 'object',但是null确实被认为是独立的基本类型,之所以会有这样的结果,是因为object是数据根据二进制转后前三位来判断的,前三位是0的会被认为是object类型,因为null全部都是0,所以也被认为是object

1.2 内置对象

除开基本类型,在object基础上,存在9个常用的内置对象Number, String, Boolean, Object, Function, Array, Date, RegExp, Error
为什么会存在这些内置对象?因为基本类型是没有提供任何的方法的,我们在Javascript的字面量或者对象调用的属性/方法,实际上是来自于内置对象。也就是说对于基本类型string,并没有length,我们在获取length属性的时候,基本类型会转换为对应的内置对象而使用内置对象的方法获取相关内容

2. 对象创建

Javascript对象创建通常有两种方法:字面量和构造方法
通过字面量的方式进行创建是最常见的。

var o = {} // 字面量,使用大括号进行创建
var o = new Object() // 构造方法,使用new的方式创建
var o = Object.create({}) // 也可以使用Object提供的方法也可以创建一个对象

3. 属性增加

3.1 计算属性

以字面量的方式为例,我们要给对象增加属性,一般在创建对象的时候同时进行属性赋值,在ES6引入了计算属性,使得属性名可以通过计算的方式设置,计算属性使用[],将需要计算的内容放到[]内部。

var pname = 'abc';
var o = {
    x: 1,
    ['a'+'b']: 2, // 计算属性
    [pname]: 3 // 计算属性,使用变量作为属性名
}

但是需要注意的一点,对于object而言,所有的属性名在设置的时候都会调用toString()的方法

var po = {};
var o = {
  [po]: 1 // po会调用toString方法
}
// 上面的对象实际上会变为
var o = {
  '[object Object]': 1
}

3.2 属性描述符

Javascript对象属性有5个属性描述符,用于对属性增加限制,分别是:writable, enumerable, configurable, get, set
我们可以使用Object.defineProperty()的给对象定义属性的时候增加属性描述

  var o = {};
  Object.defineProperty(o, 'x', {
    value: 1, // 设置属性的值, 默认undefined
    writable: false, // 设置属性是否可写,默认false
    enumerable: false, //设置属性是否可枚举,默认false
    configurable: false, // 属性描述是否可修改,默认false
    get: function(){return xxx}, // 属性get方法,也可以写作get() {}, 默认undefined
    set: function(val){}, // 属性set方法,也可以写作set(){}, 默认undefined
  })

不过要注意,这里的valuewritable不能和get(),set()同时进行设置
对于属性描述符configurable,如果设置为false,那么则不能再对对象的属性描述进行修改(writable属性从true->false是例外),否则将抛出异常

var o = {};
Object.defineProperty(o, 'x', {
  value: 1,
  configurable: false
})
Object.defineProperty(o, 'x', {
  value: 1,
  configurable: true
}) // 抛出异常Uncaught TypeError: Cannot redefine property

3.3 属性不变性

由于属性描述符可以对属性增加限制,所以可以利用这些限制来创建一些特定要求的属性,使得属性不能进行修改和重新配置,主要是用Object对象所提供的方法,来对现有对象中的属性进行处理

3.3.1 阻止对象扩展Object.preventExtensions()

使用该方法可以阻止当前的对象扩展新的属性

var o = {};
Object.preventExtensions(o) // 阻止对象增加新的属性
o.a = 1; // 严格模式下会抛出异常
console.log(o.a) // undefined
3.3.2 密封Object.seal()

使用该方法,会在Object.preventExtensions()的基础上,将对象当前所有属性的configurable设置为false

var o = {a: 1};
Object.seal(o); //密封
o.b = 1; //严格模式下抛出异常
console.log(o.b) //undefined
Object.defineProperty(o, 'a', {
    configurable: true
}) //抛出异常
3.3.3 冻结Object.freeze()

使用该方法,会在Object.seal()基础上,将当前对象所有属性的writable设置为false

var o = {a: 1};
Object.freeze(o); //冻结
o.a = 2; //严格模式下抛出异常
console.log(o.a); // 1
o.b = 1; //严格模式下抛出异常
console.log(o.b) //undefined
Object.defineProperty(o, 'a', {
    configurable: true
}) //抛出异常

3.4 数组对象中的属性

数组本身是内置对象,所以可以增加属性,当属性名为数字或者可以转换为数字时,将对数组内容进行修改,属性名为其他内容的时候并不会扩展数组对象长度

var o = [1, 2, 3];
o[3] = 4;
console.log(o.length); // 4
o['a'] = 4;
console.log(o.length); // 4

3.5 __proto__属性

对象中__proto__属性是一个特殊属性名,进行设置时如果属性值是一个object或者null,则可以改变该属性的结果,否则不能修改该属性。

var o = {__proto__: 1}
console.log(o.__proto__); // 并不等于1
var o = {__proto__: null}
console.log(o.__proto__); // undefined

3.6 方法属性

对象中的方法也是一种属性,只是和普通属性设置时存在一定的区别。从某些方面上来说,方法并不属于某个对象,因为如果方法中使用this关键字,这个this并不一定就一定指代该对象。

var o = {
  foo: function(){},
  bar() {},  // ES6新增的声明方式
  ['a'+'b']() {} // 使用计算属性进行声明
}

3.7 generator和async方法

ES6中新增的generator和async方法,也可以在对象方法属性中进行设置

var o = {
  // generator方法创建方式1 :*在方法名之前
  *g() {},  
  // generator方法创建方式2: *在function之后
  ge: function*() {}, 
  // async方法创建方式1: async关键字在方法名前
  async as() {}, 
  // async方法创建方式2: async关键字在function之前
  asy: async function() {}
};

其中关于generator和async方法的内容可以参考MDN相关说明

3.8 get和set方法

在属性设置的时候可以使用getset方法替代直接对属性进行赋值,使用getset方法替代以后,则可以在数据设置和获取时添加逻辑,进行监听了

var o = {
  get x() {return this._x_},
  set x(val) { this._x_ = val }
};
o.x = 1;
console.log(o.x); // 1

4. 属性获取

对象属性获取有两种常见的方式:使用.和使用[]

4.1 属性访问(使用.)

如果需要获取的属性名不是数字,不包含空格,那么我们通常都是使用.的方式来获取,但是对于如果属性名为数字或者包含空格,则没办法通过这样的方式获取

var o = {a: 1, 1: 'a'};
console.log(o.a); // 1
console.log(o.1); // error

4.2 键访问(使用[])

因为使用.获取对象属性的时候存在限制,所以在不能使用.的场合,我们可以使用[]来获取,但是也需要注意,对于使用[]来获取的时候,[]中的内容会调用toString()后进行属性名的匹配

var pname = {};
var pname2 = {a: 1};
var o = {a: 1, 1: 'a'};
console.log(o['a']); // 1
console.log(o[1]===o['1']); // true
o[pname] = 2; // 会被转换为o['[object Object]'] = 2;
console.log(o[pname2]); // 2,因为会被转换为 o['[object Object]']

5. 循环

5.1 in和for in

使用in关键字,可以判断一个属性是否存在对象中,但是这里会遍历对象的原型链去查找该属性

function F() { this.a = 1};
function G() { this.b = 1};
G.prototype = new F;
var o = new G;
console.log('a' in o); // true
console.log('b' in o); // true

使用for in循环,可以循环对象中所有的enumerable:true属性,同样遍历时会查找该对象原型链上的属性

function F() { this.a = 1};
function G() { this.b = 1};
G.prototype = new F;
var o = new G;
Object.defineProperty(o, 'c', {
  value: 2,
  enumerable: true // enumerable默认为false
})
for(let key in o){
  console.log(key); //  b c a
}

为了判断属性是否只在该对象中,可以使用Object.hasOwnProperty()的方法来处理结果

function F() { this.a = 1};
function G() { this.b = 1};
G.prototype = new F;
var o = new G;
for(let key in o){
  if (o.hasOwnProperty(key)) {
    console.log(key); // b
  }
}

除此之外,使用Object.keys()Object.getOwnpropertyNames()可以获取对象的属性名数组,只是这两个方法都只能获取当前对象的属性,不能获取原型链上的属性名

function F() { this.a = 1};
function G() { this.b = 1};
G.prototype = new F;
var o = new G;
console.log(Object.keys(o)); // ['b']
console.log(Object.getOwnPropertyNames(o)); // ['b']

5.2 for of

ES6中增加了for of方法来对对象进行循环,对象想要实现for of循环,需要定义[Symbol.iterator]属性,通过返回一个next方法,next方法返回一个{value:value, done: boolean}的对象,来定义对象的遍历逻辑

var o = {
  [Symbol.iterator]: function() {
    var self = this;
    var keys = Object.keys(this);
    var idx = 0;
    return {
      next() {
          return {value: self[keys[idx]], done: idx++ >= keys.length } //当done返回true的时候,循环停止
      }
    }
  }
}
o.a = 1;
o.b = 2;
for(let val of o) {
  console.log(val); // 1 2
}

6. 其他

6.1 对象复制

对象复制是一个比较复杂的内容,如果想要实现浅复制可以使用ES6新增加的Object.assign()的方法来进行,但是深复制因为需要考虑的因素很多(例如如果对象出现自引用这种复制该如何进行?),所以对于深复制并没有很好的手段,但是对于一般情况来说,可以使用JSON.parse(JSON.stringfy(obj))的方式来实现一个深复制。

6.2 新特性

对象增加了一些新特性

var o1 = {a: 1};
var o2 = {b: 2};
var o = {o1, o2} // 相当于 var o = {o1: o1 , o2: o2};
var separateO = {...o1, ...o2} // 分离属性,结果为var separateO = {a: 1, b: 2},但目前支持浏览器有限

7. 参考

《你不知道的Javascript(上篇)》
MDN Object initializer
MDN Property accessors
MDN Method definitions
MDN getter
MDN setter
MDN function*