Begin
要知道此对象非彼对象....
上一篇this那些事儿介绍了this
的指向,this
的指向是一个对象。所以很自然地提出在JavaScript中对象是什么呢?除了众所周知关于对象的知识以外,有没有什么是容易被忽略却是有趣而有用?
让我们开始吧!
介绍一下object家族的基本情况
JavaScript基本简单类型可以认为是6位成员,分别是:string
、number
、boolean
、null
、undefined
,当然还有object
。object
这一脉又有对象子类型,又被称为复杂基本类型,认识一些特殊的对象子类型:
-
function
,在js中被称为"头等"类型,因为它们基本上就是普通的对象。对象子类型中的内建对象也是function
,它的地位很特殊; -
还有九种内建对象:
String
、Number
、Boolean
、Object
、Function
、Array
、RegExp
、Date
、Error
。强烈推荐尽可能地使用字面形式的值,而非使用构造的对象形式,因为更加简单方便。像持有字符串、数字或者布尔值的变量去调用属性和方法,引擎会自动地将它转换为对象,比如:var strPrimitive = "I am a string"; strPrimitive.length; //13 strPrimitive.charAt( 3 ) //"m" 复制代码
深入了解object
object
的属性访问
属性访问是对象的特殊能力,它有两种方法,.
操作符和[]
操作符。
-
.
操作符:后面只能接标识符(字母、数字、下划线,且数字不能在首位); -
[]
操作符:[]
内放的是字符串的值,可以是obj["b"]
,也可以是var a="b";obj[a]
通过一个变量返回,间接的方式-
[]
操作符另一个使用是计算型属性名,在键声明位置指定一个表达式:var prefix = "foo"; var myObject = { [prefix + "bar"]: "hello", [prefix + "baz"]: "world" }; myObject["foobar"]; // hello myObject["foobaz"]; // world 复制代码
-
复制对象
复制对象分浅拷贝和深拷贝。
-
浅拷贝:
Object.assign()
、{...obj}
-
深拷贝:
var newObj = JSON.parse( JSON.stringify( someObj ) );
-
深拷贝的一个应用是数组去重,尤其是可以处理数组元素存在数组或者对象情况:
//数组去重 function arrElementOnly(arr) { arr = arr.map(ele => JSON.stringify(ele)); let returnArr = []; arr.forEach(ele => { if (returnArr.indexOf(ele) === -1) returnArr.push(ele); }) return returnArr.map(ele => JSON.parse(ele)); } var a = [1, 1, 2]; arrElementOnly(a); //[1,2] var b = [1, [1, 2], [1, 2] ]; arrElementOnly(b); //[1,[1,2]] var c = [1, { a: "1" }, { a: "1" }]; arrElementOnly(c); //[1,{a:"1"}] var d = [1, 1, 2, [1, 2], [1, 2], { a: "1" }, { a: "1" } ]; arrElementOnly(d); //[1,2,[1,2],{a:"1"}] 复制代码
-
属性描述符
属性描述符分为数据描述符和访问器描述符。数据描述符使用Object.getOwnPropertyDescriptor(...)
查看,使用Object.defineProperty(...)
定义,看个例子:
var myObject = {
a: 2
};
Object.getOwnPropertyDescriptor( myObject, "a" ); //查看数据描述符
// {
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true
// }
Object.defineProperty(myObject,"b",{ //定义数据描述符
value:3,
writable:true,
configurable:true,
enumerable:true
})
myObject.b; //3
复制代码
writable
控制着改变属性值的能力,writable:false
在非严格模式下修改悄无声息失败,严格模式下抛出TypeError
configurable
为true
时,属性可配置,使用defineProperty(...)
修改数据描述符configurable:false
,defineProperty(...)
修改数据描述符,无论strict mode
与否,都会抛出TypeError
,这是一个单向操作不可撤销(例外是在这个情况下,writable
可由true
变为false
)configurable:false
阻止的另外一个事情是使用delete
操作符移除既存属性的能力 ,delete
调用无声失败
enumerable
可枚举性,控制着属性是否能在特定的对象-属性枚举操作中出现
谈访问器描述符之前先要谈[[Get]]
和[[Put]]
操作:
[[Get]]
操作定义了对象属性访问的行为,简单来说如果当前对象不存在该属性,就会遍历[[Prototype]]
链,如果遍历原型链不存在,返回undefined
[[Put]]
操作定义设置属性值的行为, 简单来说,属性存在访问器描述符setter吗? 调用setter:(writable=false
?修改失败或者抛出TypeError
:设置属性值)
访问器描述符针对某个属性覆盖[[Get]]、[[Put]]默认操作的一部分,看getter和setter:
//字面量语法定义
var myObject = {
// 为 `a` 定义 getter
get a() {
return this._a_;
},
// 为 `a` 定义 setter
set a(val) {
this._a_ = val * 2;
}
};
myObject.a = 2;
myObject.a; // 4
//使用defineProperty定义
Object.defineProperty(myObject,"b",{
get:function(){return this.a},
enumerable:true
})
复制代码
注意:访问器描述符不能和writable
、value
一起设置,会报错Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
。
应用
-
将属性或对象设置为不可改变(p.s. 创建的是浅不可变性,如果对象拥有对其他对象的引用,如数组、对象、函数等 ,那个对象的内容不会受影响,依然保持可变),技术上操作数据操作符:
- 设置一个对象属性为常量(不能被改变、重定义、删除):
defineProperty(...)
设置属性writable:false
和configurable:false
- 防止一个对象添加新的属性:
Object.preventExtensions(...)
- 对象不可添加新的属性,对象所有属性不能重定义、不能删除(属性值可以更改):
Object.seal(...)
,相当于Object.preventExtensions(...)
+所有对象属性configurable:false
- 对象不可添加新属性,对象所有属性设置为常量:
Object.freeze(...)
,相当于Object.preventExtensions(...)
+所有对象属性configurable:false
、writable:false
- 设置一个对象属性为常量(不能被改变、重定义、删除):
-
在属性访问时得到
defined
有两种情况,一种属性不存在返回undefiend
,另一种属性明确存储值undefined
,那么该如何区分?引入属性存在性判断的问题。看个例子:var myObject = { a: 2 }; ("a" in myObject); // true ("b" in myObject); // false myObject.hasOwnProperty( "a" ); // true myObject.hasOwnProperty( "b" ); // false 复制代码
in
操作符会检查[[Prototype]]
链,而hasOwnProperty(...)
只会检查当前对象。你能猜到4 in [2,4,6]
的结果是什么吗? -
属性枚举性判断。看个例子:
var a = { name: "Harden" }; var b = Object.create(a); b.age = 18; Object.defineProperty(b, "hobby", { value: "basketball", enumerable: false }); for (pro in b) { console.log(b[pro]); //Harden,18 } console.log(b.propertyIsEnumerable("name")); //fasle 当前对象不存在 console.log(b.propertyIsEnumerable("age")); //true console.log(b.propertyIsEnumerable("hobby")); //false 属性不可枚举 console.log(Object.keys(b)); //["age"] console.log(Object.getOwnPropertyNames(b)); //["age","hobby"] 复制代码
总结一下和枚举型属性相关的几个方法:
for..in
是遍历对象可枚举属性,会检查原型链Object.keys
返回给定对象自身可枚举属性组成的数组,不检查原型链propertyIsEnumerable(...)
判断对象自身是否存在给定属性,不检查原型链
另外
Object.getOwnPropertyNames()
方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。 -
for..of
循环迭代值,要求被迭代的东西提供一个迭代器对象。先看一下数组:数组使用
for..of
迭代是ok的,因为数组拥有内建的@@iterator
。 使用Symbol.iterator
取得数组的一个内建对象@@iterator
,@@iterator
是一个函数,函数运行返回一个迭代器对象,这样就可以it.next()
迭代。var a = [1, 2, 3, 4]; for (value of a) { console.log(value); 1,2,3,4 } //手动使用@@iterator var b = [1,2,3]; var it = b[Symbol.iterator](); console.log(it.next()); //{ value:1, done:false } console.log(it.next()); //{ value:2, done:false } console.log(it.next()); //{ value:3, done:false } console.log(it.next()); //{ done:true } 复制代码
再看对象:
对象没有内建的
@@iterator
,所以抛出TypeError
了。var myObject = { a: 2, b: 3 }; for (value of myObject) { console.log(value); } //oops! Uncaught TypeError: myObj is not iterable 复制代码
但是我们也可以自定义迭代器:
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() { let value = o[ks[idx++]], done = idx > ks.length; return { value, done }; } }; } }); //手动迭代 var it = myObject[Symbol.iterator](); console.log(it.next()); console.log(it.next()); console.log(it.next()); for (value of myObject) { console.log(value); } 复制代码
自定义迭代器存在一定可操作空间,想想你可以设置迭代返回的结果,是不是很赤激!自己想去吧。
补充:
对象属性遍历的方法:
-
Object.entries(...)
:对象自身的可枚举属性,返回键值对的数组,demo如下:var objF = { name: "Zhong" } var objC = { age: 22, hobby: "basketball" } Object.setPrototypeOf(objC, objF); Object.entries(objC); //[["age",22],["hobby","basketball"]] 复制代码
-
Object.keys
:对象自身的可枚举属性,返回键的数组,demo如下:Object.keys(objC); //["age","hobby"] 复制代码
-
Object.getOwnPropertyNames
:对象自身属性,返回键的数组; -
Reflect.ownKeys(...)
:对象自身属性,返回键的数组;
Reflect.ownKeys
方法返回一个由目标对象自身的属性键组成的数组。它的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。
只有Reflect.ownKeys
会得到Symbol
类型的属性!
-
Object.values(...)
:对象自身的可枚举属性,返回值的数组,demo如下:Object.values(objC); //[22,"basketball"] 复制代码
-
for..in
:检测原型链的可枚举属性,返回键
对应的顺序规则:
-
如果属性名类型转为
string
后是整数,那么顺序是按照key从小到大排序 ;如果属性名是其他的string
或者symbol
类型,那么顺序是按照属性被创建的时间。demo如下:var obj1 = { 2:2, 10:10, //整数 "8":8, //字符串的值是整数 6:6 } //Object {2: 2, 6: 6, 8: 8, 10: 10} var obj2 = { name:"Harden", age:22, hobby:"basketball" } //Object {name: "Harden", age: 22, hobby: "basketball"} 复制代码
-
如果出现混合,顺序分别是"整数",
string
,symbol
。demo如下:var obj3 = { name:"Harden", 5:5, age:22, 1:1, hobby:"basketball", 1.2:1.2 //这是个特例,它不是整数 } //Object {1: 1, 5: 5, name: "Harden", age: 22, hobby: "basketball", 1.2: 1.2} 复制代码
End
文章为个人总结,不妥之处还请雅正。
转载请注明出处。
参考文献: