JavaScript之Object拆解
转载烦请注明原文链接:
https://github.com/Xing-Chuan/blog/blob/master/JavaScript/JavaScript之Object拆解.md
最近把研究 Object 的体会总结了一下, 有 Object 相关联的属性、方法和 ES6 后新 Api .
属性类型
JavaScript 中有两种数据类型: 数据属性和访问器属性
数据属性
数据属性有以下几个描述行为的属性:
- Configurable 描述这个属性是否可被 delete 删除, 默认为 true
- Enumerable 描述这个属性是否可被枚举, 默认为 true
- writable 描述这个属性是否可被修改, 默认为 true
- value 描述这个属性的值
如果想要修改这些系统默认属性, 可以通过 ES5 的方法 Object.defineProperty(obj, property, option).
注意:
- 可以多次调用 Object.defineProperty() 修改属性, 但将 Configurable 设置为 false 是不可逆的.
- 调用 Object.defineProperty() 修改属性方法时, 如果不指定这些描述属性, 则默认值都会设置为 false.
访问器属性
访问器有以下几个属性:
- Configurable 描述这个属性是否可被 delete 删除, 默认为 true
- Enumerable 描述这个属性是否可被枚举, 默认为 true
- get 在读取属性时调用的函数, 默认为 undefined
- set 在设置属性时调用的函数, 默认为 undefined
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
注意:
- get 和 set 必须同时设置, 否则会出现不能读或不能写的情况
如果要一次修改多个参数的描述属性, 可以使用 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 的实例, 所以构成了这一条原型链.
注意:
- 原型链的访问是有就近原则的, 如果像上面实例中已经有 name 属性, 则不会继续访问 prototype 上的 name 属性, 如果实例中没有这个属性, 则会按照原型链一直找下去, 如果没有就是 undefined.
元素也是 Object 的实例
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 - Function 也是自己的实例
- 一切起源于
Object.prototype - 每个原型都有
constructor属性, 指向原型所属的函数, 用字面量对象改为原型的引用, 会丢失constructor这个属性
来张示意图结尾( 侵删 ):

this 的指向
<JavaScript忍者禁术> 一书中对 this 指向的问题有比较详细的描写, 推荐看看.
在讨论 this 指向的问题之前, 先要明确一下, 在严格模式下, 未指定环境对象而调用函数,则 this 值不会转型为 window.
除非明确把函数添加到某个对象或者调用 apply()或 call(),否则 this 值将是 undefined.
- 普通函数中的 this
let x = 1;
function show() {
console.log(this.x);
}
show(); // 1
普通函数中的 this 指向 window
- 构造函数中的 this
function Info(){
this.x = 1;
}
let info1 = new Info();
console.log(info1.x); // 1
构造函数中的 this 指向 new 出来的实例对象, new 这个操作符会改变 this 的指向.
- 对象方法中的 this
let x = 2;
let obj = {
x: 1,
y: function() {
console.log(this.x);
}
};
obj.y(); // 1
对象方法中的 this 指向调用它的对象.
- call、apply、bind(ES5) 中的 this
call、apply、bind(ES5) 的作用就是改变 this 的指向, this 会指向第一个参数.
如果 call、apply、bind(ES5) 调用方法时没有传参, 默认 this 指向 window.
bind 只会改变 this 的指向, 并不会执行方法, call 和 apply 则会改变指向时也执行方法.
- 事件函数中的 this
事件函数中的 this 指向绑定事件的元素.
- 箭头函数中的 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]"
可以全部搞定了.
ES6 之后的新特性
对象属性的简洁写法
简洁的写法可以减少代码量也可以更加优雅, 但代码是给计算机看的, 同时也是给人看的, 容易发生歧义的地方一定要注意.
- 属性的简写
如果属性名与属性值相同, 可以忽略不写.
属性值是字符串时不可简写.
// 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('','') 同值相等API
Object.is() 基本等同于 === , 除却两点:
+0 === -0 // true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.assign(target, source1, source2) 复制可枚举属性
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 的属性:
- for...in 循环
- Object.keys()
- JSON.stringify()
ES6 新增的 Object.assign() 也会忽略描述中不可枚举的属性.
数组中的 length 属性不会被 for...in 获取就是因为不可枚举的描述.
Object.getOwnPropertyDescriptor([1,2,3],'length');
// {
// configurable: false,
// enumerable: false,
// value: 3,
// writable: true
// }
另外, ES6 中也规定了 Class 原型上的方法是不可枚举的.
属性的遍历
- for...in
for...in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
- Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)
- Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)
- Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性
- Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管属性名是 Symbol 或字符串,也不管是否可枚举
以上 5 种方法在遍历顺序上, 遵循以下 3 条规则:
- 首先遍历所有属性名为数值的属性,按照数字排序
- 其次遍历所有属性名为字符串的属性,按照生成时间排序
- 最后遍历所有属性名为 Symbol 值的属性,按照生成时间排序
__proto__, Object.getPrototypeOf(), Object.setPrototypeOf()
__proto__
__proto__ 指向当前实例的原型对象, 其没有被 ES6 列为正式 API, 但因为被浏览器厂商广泛使用, 被收入附录.
某些浏览器厂商同样指向原型对象, 可能是另一种命名方式, 所以为了兼容性考虑, 最好不要通过它去操作原型.
Object.setPrototypeOf()
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()
Object.getPrototypeOf(obj) 是 ES6 返回原型对象的方法
Object.keys(), Object.values(), Object.entries()
Object.keys() 是 ES5 中遍历属性的方法, ES6 新增了 Object.values(), Object.entries().
- Object.values
返回对象自身的(不包含继承的), 可枚举的键值
- Object.entries()
返回对象自身的(不包含继承的), 可枚举的键值对数组
对象的拓展运算符
ES8 中将数组的拓展运算符引入到了对象中.
解构
let {a, b, ...x} = {a: 1, b:2, c: 3, d: 4};
console.log(x);
// {c:3,d:4}
注意:
- 拓展运算符会复制对象所有未读取的键值对, 所以右侧必须是对象或可转换为对象, 不能为 null 或 undefined
- 拓展运算符必须置为最后一个位置, 否则会报错
- 拓展运算符不会复制原型上的方法
克隆对象
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');
}
}
};
Object.getOwnPropertyDescriptors()
ES5 中 Object.getOwnPropertyDescriptor(obj, property) 可以获取对象属性的描述对象.
ES8 中 新增了 Object.getOwnPropertyDescriptors(obj) 可以获取对象所有属性的描述对象, 描述对象包括 get 和 set 属性.
Null 传导运算符
我们要读取对象的一个属性或调用其方法, 为了不报错, 应该先判断对象是否存在, 然后再读取其属性.
如果我们想读取 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';
参考资料
- MDN
- JavaScript高级程序设计
- ECMAScript6入门
- JavaScript忍者禁术
JavaScript之Object拆解的更多相关文章
- 从头开始学JavaScript (十一)——Object类型
原文:从头开始学JavaScript (十一)--Object类型 一.object类型 一个object就是一系列属性的集合,一个属性包含一个名字(属性名)和一个值(属性值). object对于在应 ...
- JavaScript中Object的总结
基于原型继承,动态对象扩展,闭包,JavaScript已经成为当今世界上最灵活和富有表现力的编程语言之一. 这里有一个很重要的概念需要特别指出:在JavaScript中,包括所有的函数,数组,键值对和 ...
- JavaScript Math Object 数字
JavaScript Math Object Math Object The Math object allows you to perform mathematical tasks. Math is ...
- JavaScript中Object.prototype.toString方法的原理
在JavaScript中,想要判断某个对象值属于哪种内置类型,最靠谱的做法就是通过Object.prototype.toString方法. ? 1 2 var arr = []; console.lo ...
- 【WIP】客户端JavaScript Web Object
创建: 2017/10/11 更新: 2017/10/14 标题加上[WIP],增加[TODO] 更新: 2018/01/22 更改标题 [客户端JavaScript Web Object, UR ...
- JavaScript中object和Object有什么区别
JavaScript中object和Object有什么区别,为什么用typeof检测对象,返回object,而用instanceof 必须要接Object呢 ————————————————————— ...
- javascript nested object merge
javascript nested object merge deep copy Object reference type function namespace(oNamespace, sPacka ...
- Javascript判断object还是list/array的类型(包含javascript的数据类型研究)
前提:先研究javascript中的变量有几种,参考: http://www.w3school.com.cn/js/js_datatypes.asp http://glzaction.iteye.co ...
- javascript的 Object 和 Function
一. javascript 的 内置对象: Object 和 Function javascript所有东西,包括 Function 都是对象 . Array 其实是一个 Function 类型的对 ...
随机推荐
- Linux文件管理浅析(一) _磁盘管理基础
本文主要讨论一些磁盘管理相关的基本概念,同时也是这一系列文章的第一篇,就是下图中的最下一层的一部分. 在Linux中,SATA/USB/SCSI接口都是使用SCSI模块实现的,所以使用这些接口的硬盘在 ...
- 【LeetCode】98. Validate Binary Search Tree
题目: Given a binary tree, determine if it is a valid binary search tree (BST). Assume a BST is define ...
- 4.vbs的循环,switch,判断等语句
1.条件判断语句 If Then Else Sub judge(x) Then MsgBox "the num is 0" Then MsgBox "the num is ...
- word2-寻找社交新浪微博中的目标用户
项目简述: 为了进行更加精准的营销, 利用数据挖掘相关算法, 利用开放API或自行编写爬虫获得新浪微博, 知乎等社交网络(可能需要破解验证码)中用户所发布的数据, 利用数据挖掘的相关算法进行分析, 从 ...
- Vijos 1033 整数分解(版本2)
描述 整数分解(版本2) 一个正整数可以分解成若干个自然数之和.请你编一个程序,对于给出的一个正整数n(1<=n<=1500),求出满足要求的分解方案,并使这些自然数的乘积m达到最大. 例 ...
- Java IO学习笔记(四)打印流
1.只有输出流才有打印流:PrintWriter和PrintStream分别针对字符和字节,提供了重载的print,Println方法用于多种数据类型的输出.PrintWriter和PrintStre ...
- .NET 跨平台界面框架和为什么你首先要考虑再三
原文地址 现在用 C# 来开发跨平台应用已经有很成熟的方案,即共用非界面代码,而每个操作系统搭配特定的用户界面代码.这个方案的好处是可以直接使用操作系统原生的控件和第三方控件,还能够和操作系统 ...
- Java编程练习(四)——集合框架应用
Java集合框架小应用之扑克牌小游戏 学习了Java集合框架之后,我写了一个扑克牌小游戏来巩固知识.学习之余的练习之作,有不足之处还得多多指教了~(*/ω\*) 扑克牌小游戏背景: 1. 创建一副扑克 ...
- python编程快速上手之第4章实践项目参考答案
#!/usr/bin/env python3.5 # coding:utf-8 # 假定有一个列表,编写函数以一个列表值作为参数,返回一个字条串 # 该字符串包含所有表项,之间以逗号和空格分隔,并在最 ...
- windows安装程序无法将windows配置为在此计算机的硬件上运行
关于装windows系统时,出现一些安装中断的处理 该方法适用于 windows安装程序无法将windows配置为在此计算机的硬件上运行 计算机意外地重新启动或遇到错误. Windows 安装无法继续 ...