转载烦请注明原文连接:
https://github.com/Xing-Chuan/blog/blob/master/JavaScript/JavaScript%E4%B9%8BObject%E6%8B%86%E8%A7%A3.md
---javascript
最近把研究 Object 的体会总结了一下, 有 Object 相关联的属性、方法和 ES6 后新 Api .java
JavaScript 中有两种数据类型: 数据属性和访问器属性git
数据属性有如下几个描述行为的属性:es6
若是想要修改这些系统默认属性, 能够经过 ES5 的方法 Object.defineProperty(obj, property, option).github
注意:chrome
访问器有如下几个属性:api
let book = { _year: 2004, edition: 1 }; /* * 第一个参数: 对象 * 第二个参数: 对象的属性 * 第三个参数: 须要设置的描述属性 */ Object.defineProperty(book, 'year', { get: function() { return this._year; }, set: function(newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2009; console.log(book.edition); // 6
注意:数组
若是要一次修改多个参数的描述属性, 可使用 Object.defineProperties()浏览器
let book = {}; Object.defineProperties(book, { _year: { value: 2004 }, edition: { value: 1 } });
构造函数也是普通的函数, 若是没有经过 new 操做符生成新的实例对象, 那构造函数就是一个普通的函数.
为了区分构造函数于普通函数, 咱们一般用大驼峰命名法给构造函数命名.
String、Array 等几乎全部对象都是 Object 的实例, Object 就是一个构造函数.安全
new Array() instanceof Object // true new String() instanceof Object // true new Function() instanceof Object // true
function Info() { this.name = 'XingChuan'; this.age = '10'; } let info1 = new Info(); let info2 = new Info(); console.log(info1.name); // XingChuan console.log(info1.age); // 10 console.log(info2.name); // XingChuan console.log(info2.age); // 10
既然谈到构造函数就要谈到原型了, 每一个函数都有一个原型(prototype)属性, 原型存在的意义就是, 原型上的属性和方法由全部构造函数的实例对象所共享, 若是修改了原型对象上的方法, 那全部实例都会实时更改.
咱们平时使用 Array 和 String 的方法, 都是在其原型对象上, 全部咱们建立的全部数组和字符串均可以享有这些方法.
在 chrome, firefox, safari 中, 有一个浏览器私有属性 __proto__
指向构造函数的 prototype, __proto__
并非官方属性, 为了兼容性考虑, 开发中最好不要使用 __proto__
.
下面咱们来具象化一下原型链的构成:
构造函数也是普通的函数, 只是咱们拿来生成实例, 因此才有这个称谓, 而全部的函数都是 Function 的实例.
function info() { }
// 原型链 info.__proto__ => Function.prototype => Object.prototype
全部的函数都是 Function 的实例, 而 Function 是 Object 的实例, 因此有了这条原型链.
颇有意思的是, Object、Array、String、Function 都是函数, 因此他们都是 Function 的实例, Function 比较特殊, 它也是自身的实例.
Math 是个例外, 它并非一个函数, 而是一个对象.
console.log(Array instanceof Function) // true console.log(String instanceof Function) // true console.log(Object instanceof Function) // true console.log(Function instanceof Function) // true console.log(Math instanceof Function) // false console.log(Math.__proto__ === Object.prototype); // true
// 原型链 Array.__proto__ => Function.prototype => Object.protytype
function Info() { } Info.prototype.name = 'Xingchuan'; Info.prototype.age = 10; Info.prototype.showName = function() { console.log(this.name); }; let info1 = new Info(); info1.showName() // XingChuan let info2 = new Info(); info2.name = 'test'; info2.showName() // test
// 原型链 info1.__proto__ => Info.prototype => Object.prototype
实例对象的 __proto__
会指向构造函数的 prototype, 全部的原型对象都是 Object.prototype 的实例, 因此构成了这一条原型链.
注意:
console.dir(document.getElementsByTagName('span')[0]); // span元素 => HTMLSpanElement => HTMLElement => Element => Node => EventTarget => Object.prototype console.dir(document.getElementsByTagName('span')[0] instanceof Object); // true
元素的原型链很长, 不过能够看到元素也是 Object 的实例.
prototype
的 constructor 指向 构造函数__proto__
指向构造函数的 prototype
__proto__
指向 Function.prototype
prototype
都是基于 Object.prototype
Object.prototype
constructor
属性, 指向原型所属的函数, 用字面量对象改成原型的引用, 会丢失 constructor
这个属性来张示意图结尾( 侵删 ):
在讨论 this 指向的问题以前, 先要明确一下, 在严格模式下, 未指定环境对象而调用函数,则 this 值不会转型为 window.
除非明确把函数添加到某个对象或者调用 apply()或 call(),不然 this 值将是 undefined.
let x = 1; function show() { console.log(this.x); } show(); // 1
普通函数中的 this 指向 window
function Info(){ this.x = 1; } let info1 = new Info(); console.log(info1.x); // 1
构造函数中的 this 指向 new 出来的实例对象, new 这个操做符会改变 this 的指向.
let x = 2; let obj = { x: 1, y: function() { console.log(this.x); } }; obj.y(); // 1 对象方法中的 this 指向调用它的对象.
call、apply、bind(ES5) 的做用就是改变 this 的指向, this 会指向第一个参数.
若是 call、apply、bind(ES5) 调用方法时没有传参, 默认 this 指向 window.
bind 只会改变 this 的指向, 并不会执行方法, call 和 apply 则会改变指向时也执行方法.
事件函数中的 this 指向绑定事件的元素.
箭头函数是 ES6 中新增的方法, 不一样于其余状况中的 this 在调用时才决定指向, 箭头函数 this 指向定义时的外围, 在不确认指向的状况下, 请慎用.
ECMAScript 6 入门对箭头函数的使用限制作了说明: (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。 (2)不能够看成构造函数,也就是说,不可使用new命令,不然会抛出一个错误。 (3)不可使用arguments对象,该对象在函数体内不存在。若是要用,能够用Rest参数代替。 (4)不可使用yield命令,所以箭头函数不能用做Generator函数。
在开发中, 咱们能够用 typeof 来判断类型, 但有很大的局限性.
typeof 1 // "number" typeof '1' // "string" typeof true // "boolean" typeof undefined // "undefined" typeof null // "object" typeof (function(){}) // "function" typeof [] // "object" typeof {} // "object"
typeof 只对一些简单数据类型有效, 为了能够判断各类内置对象, 咱们须要采起一些 手段
, 使用 Object 原型上的 toString 方法.
Object.prototype.toString.call(1); // "[object Number]" Object.prototype.toString.call('1'); // "[object String]" Object.prototype.toString.call(true); // "[object Boolean]" Object.prototype.toString.call(function() {}); // "[object Function]" Object.prototype.toString.call(null); // "[object Null]" Object.prototype.toString.call(undefined); // "[object Undefined]" Object.prototype.toString.call(new Date()); // "[object Date]" Object.prototype.toString.call(Math); // "[object Math]" Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call({}); // "[object Object]"
能够所有搞定了.
简洁的写法能够减小代码量也能够更加优雅, 但代码是给计算机看的, 同时也是给人看的, 容易发生歧义的地方必定要注意.
若是属性名与属性值相同, 能够忽略不写.
属性值是字符串时不可简写.
// old let name = 'XingChuan'; let obj = { name: name }; // new let name = 'XingChuan'; let obj = { name }; // error let name = 'XingChuan'; let obj = { name:'name' };
// old let obj = { show: function() { console.log('show'); } }; // new let obj = { show() { console.log('show'); } };
ES5只支持这种字面量定义:
let obj = { name: 'XingChuan', age: 10 };
ES6支持这种写法:
let obj ={ [name]: 'XingChuan', ['a' + 'ge']: 10 }; // 做为属性名的表达式会自动 toString() , 应避免使用对象做为表达式, 由于 String({}) === '[object Object]'
注意:
Object.is()
基本等同于 ===
, 除却两点:
+0 === -0 // true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
let target = { info: { name: 'name1' } }; let source1 ={ info: { name: 'name2', age: 30 } }; let source2 ={ info: { name: 'name3' } }; Object.assign(target, source1, source2); // { info: {name: 'name3'}} // // Object.assign 是浅拷贝, 若是属性的值是对象, 就会添加新的引用, 而不是在原有地址上添加属性. // target会自动转换为对象, 因此不能为 null 或 undefined , 会报错 // source为 null 或 undefined 时, 由于没法转换为对象, 会跳过, 但不会报错 // 若 source 为字符串, 会以数组的形式复制到 target 中 //
每一个对象属性都有一个描述对象 Description , 能够控制是否可被枚举, 数据属性的其中之一.
let obj = { name: 'XingChuan' }; Object.getOwnPropertyDescriptor(obj,'name'); // { // configurable: true, // enumerable: true, // 若是可枚举为 false , 某些操做会忽略掉这个属性 // value: "XingChuan", // writable: true // }
ES5 中有 3 个属性会忽略 enumerable
为 false
的属性:
ES6 新增的 Object.assign() 也会忽略描述中不可枚举的属性.
数组中的 length 属性不会被 for...in 获取就是由于不可枚举的描述.
Object.getOwnPropertyDescriptor([1,2,3],'length'); // { // configurable: false, // enumerable: false, // value: 3, // writable: true // }
另外, ES6 中也规定了 Class 原型上的方法是不可枚举的.
for...in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
Object.keys返回一个数组,包括对象自身的(不含继承的)全部可枚举属性(不含 Symbol 属性)
Object.getOwnPropertyNames返回一个数组,包含对象自身的全部属性(不含 Symbol 属性,可是包括不可枚举属性)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的全部 Symbol 属性
Reflect.ownKeys返回一个数组,包含对象自身的全部属性,无论属性名是 Symbol 或字符串,也不论是否可枚举
以上 5 种方法在遍历顺序上, 遵循如下 3 条规则:
__proto__
, Object.getPrototypeOf(), Object.setPrototypeOf()__proto__
__proto__
指向当前实例的原型对象, 其没有被 ES6 列为正式 API, 但由于被浏览器厂商普遍使用, 被收入附录.
某些浏览器厂商一样指向原型对象, 多是另外一种命名方式, 因此为了兼容性考虑, 最好不要经过它去操做原型.
Object.setPrototypeOf() 是 ES6 设置原型对象的方法
let obj = { x: 10 }; let option = { x: 20, y: 30, z: 40 }; Object.setPrototypeOf(obj, option); obj.x // 10 obj.y // 30 obj.z // 40 // 因原型链访问顺序的优先级, obj.x 为 10 而不是 20, 如 obj 不存在 x 的属性, obj.x 就会为 20.
Object.getPrototypeOf(obj) 是 ES6 返回原型对象的方法
Object.keys() 是 ES5 中遍历属性的方法, ES6 新增了 Object.values(), Object.entries().
返回对象自身的(不包含继承的), 可枚举的键值
返回对象自身的(不包含继承的), 可枚举的键值对数组
ES8 中将数组的拓展运算符引入到了对象中.
let {a, b, ...x} = {a: 1, b:2, c: 3, d: 4}; console.log(x); // {c:3,d:4}
注意:
let x = {name: 'XingChuan', age: 88}; let cloneObj = { ...x };
let x = {name: 'XingChuan', age: 88}; let y = {job: 'developer'}; let cloneObj = { ...x, ...y };
let obj = { ...{x > 1 ? {a: 1} : {} } };
扩展运算符的参数对象之中,若是有取值函数get,这个函数是会执行的.
let runtimeError = { ...a, ...{ get x() { throws new Error('thrown now'); } } };
ES5 中 Object.getOwnPropertyDescriptor(obj, property) 能够获取对象属性的描述对象.
ES8 中 新增了 Object.getOwnPropertyDescriptors(obj) 能够获取对象全部属性的描述对象, 描述对象包括 get 和 set 属性.
咱们要读取对象的一个属性或调用其方法, 为了避免报错, 应该先判断对象是否存在, 而后再读取其属性.
若是咱们想读取 obj.info.xingchuan.name, 安全的写法应该是下面这样
let name = obj && obj.info && obj.info.xingchuan && obj.info.xingchuan.name || 'default';
如今提案中引入了 Null 传导运算符, 简化了写法, 能够写为下面这种方式.
let name = obj ?. info ?. xingchuan ?. name || 'default';