[JS] You Don't Know JavaScript [this & Object Proto

前言

在Object [上]中我们介绍了物件的宣告、型态、拷贝等等特性,接下来我们继续介绍物件中都有哪些特性。

Property Descriptors

在ES5之前JS没有提供一个可以区分物件属性特徵的方法,但在ES5中可以透过property descriptor观察物件属性的特性。

var myObject = {a: 2};Object.getOwnPropertyDescriptor( myObject, "a" );// {//    value: 2,//    writable: true,//    enumerable: true,//    configurable: true// }

我们对myObject中的属性a使用getOwnPropertyDescriptor,可以得到除了value的另外三个特质:writableenumerableconfigurable

既然我们可以拿到这个数性的特质,那们我们也可透过Object.defineProperty(...)来添加新的特性或是修改原本的特性。

var myObject = {};Object.defineProperty( myObject, "a", {value: 2,writable: true,configurable: true,enumerable: true} );myObject.a; // 2

一班如果你要加入一个正常的属性到物件中是不必要使用Object.defineProperty这个方法,除非你想新增特殊型态的属性。

Writable

此特性代表你是否能够更改属性的值。

var myObject = {};Object.defineProperty( myObject, "a", {value: 2,writable: false, // not writable!configurable: true,enumerable: true} );myObject.a = 3; // can't re-assignmentmyObject.a; // 2

对于myObject中的a属性设定为不可写入则我们无法再次赋值给他,如果这个程式在严格模式下执行,会掷出TypeError

"use strict";var myObject = {};Object.defineProperty( myObject, "a", {value: 2,writable: false, // not writable!configurable: true,enumerable: true} );myObject.a = 3; // TypeError : Cannot change a non-writable property.

Configurable

Configurable特性控制着我们能否使用defineProperty(...)重新改变属性的特性。

var myObject = {a: 2};myObject.a = 3;myObject.a;// 3Object.defineProperty( myObject, "a", {value: 4,writable: true,configurable: false,// not configurable!enumerable: true} );myObject.a;// 4myObject.a = 5;myObject.a;// 5Object.defineProperty( myObject, "a", {value: 6,writable: true,configurable: true,enumerable: true} ); // TypeError

一旦将物件属性的configurable设定为false后,就算不是处于严格模式下,再次尝试更改数性的设置就会掷出TypeError,这意味着当你设置属性的configurable为false后就不可逆转

当你将这个属性的configurable设定为false后可以避免这个属性被delete操作符移除。

var myObject = {a: 2};myObject.a;// 2delete myObject.a;myObject.a;// undefined// configurable:falseObject.defineProperty( myObject, "a", {value: 2,writable: true,configurable: false,enumerable: true} );myObject.a;// 2delete myObject.a;myObject.a;// 2

在JS中delete操作符仅用于移除物件的属性,如果一个物件中的属性引用的是某一个物件/函数的最后一个现存引用,那们当你delete这个属性那们就代表移除了最后一个引用,而这个没有被任何地方引用的物件/函数则会被回收掉,但如果还有其他地方引用则只是单纯地从这个物件中移除他们的Reference总结来说delete仅仅是移除物件的属性而已

Enumerable

Enumerable特性决定了我们是否可以对这个元素进行列举操作(for...in),如果将这个特性设定为false则for...in loop终究无法读取到这个属性

Object.defineProperty( myObject, "a", {value: 2,writable: true,configurable: ture,enumerable: false} );myObject.b = 2;myObject.b = 4;for(let prop in myObject){    console.log(prop); // disable property a}// b// c

Immutability

有时候我们在开发专案的时候会需要建立一些不希望被更改的物件,ES5中提供了一些方法可以让我做到让物件保持不被更改,但是直得注意的是:这些方法都是浅层不变性,这意味着他只会固定住本体物件和他的直属属性性质,但是如果这个被固定住的物件中有引用其他物件的属性(阵列、物件、函数),那么这些被Reference的物件属性并不会跟着被锁定住

Object Constant

通过将物件属性writableconfigurable都设定为false让我们的物件保持不能被更改的状况。

var myObject = {};Object.defineProperty( myObject, "FAVORITE_NUMBER", {value: 42,writable: false,configurable: false} );

Prevent Extensions

如果要禁止添加属性到我们的物件中,可以使用Object.preventExtensions(...)

var myObject = {a: 2};Object.preventExtensions( myObject );myObject.b = 3;myObject.b; // undefined

在非严格模式下b的属性添加会失败,否则会掷出TypeError。

Seal

使用Object.seal(..)创建一个被封印的物件,实际上是将这个物件使用Object.preventExtensions(...)固定住不允许加入其他属性在对物件中的每一个属性标记为configurable:false让物件中的元素也不能被删除或重新配置,但是可以对现有存在的值进行修改

Freeze

使用Object.freeze(...)创建一个冻结的物件,实际上是将物件使用Object.seal(..)使他不能添加、删除、重新配置新的属性,再对每个属性使用writable:false让物件属性的值不能被更改,这个方法是最高级别的不可变性。


[[Get]]

关于对于物件属性的访问有个重要的细节。

var myObject = {a: 2};myObject.a; // 2

使用myObject.a来访问物件中的a属性,但这个操作不只是在物件中查找名称为a属性,实际上他会先在myObject上执行一个[[Get]]操作,他会检查物件并在物件中寻找有没有这个被请求的属性名称,如果找到就返回相对应的值,如果没能在物件中找到属性则会做另一个重要的事情(遍历[prototype]链),如果通过任何方法都找不到这个属性的时候便会返回undefined

var myObject = {a: 2};myObject.b; // undefined

[[Put]]

既然从一个属性中取得值存在一个内部定义的[[Get]]方法,那么也会有一个内部定义的[[Put]]。

调用[[Put]]时会根据几个因素表现不同的行为,影响最大的辨识属性是否已经在存在于物件,如果属性存在[[Put]]会检查:

如果这个属性是访问器描述符号(Getters/Setters)中的Setters,则直接调用Setters。如果这个属性的writable是false,在非严格模式下无声的失败,否则掷出TypeError。不符合以上两个则像正常赋值一样设置属性。

如果这个属性不存在于物件中则[[Put]]操作会更複杂,我们在后面会详细讲解。


Getters & Setters

ES5中新增了一个方法来覆盖这些默认操作的一部分,不是针对每个属性而不是物件,就是通过getterssetters,Getter 是用来取得指定属性的值的方法而Setter是用来设定指定属性的值的方法

var myObject = {// define a getter for `a`get a() {return 2;}};myObject.a = 3;myObject.a; // 2

我们仅为a定义了一个getter,就算之后向再次对a赋值也不会成功,他会无声的将赋予的值丢弃而不会掷出错误,我们可以多新增一个setters让我们可以更改a的值。

var myObject = {// define a getter for `a`get a() {return this.b;},// define a setter for `a`set a(val) {this.b = val * 2;}};myObject.a = 2;myObject.a; // 4

当我们重新赋予值给a,他会调用myObject中的set(...),他将我们给的值*2后暂存在一个变量b,之后再透过get(...)将这个值传回给a。


Existence

我们可以透过in操作符来确认我的数性是否有存在在物件中。

var  myObject  =  { a : undefined } ;console.log(myObject.a); // undefinedconsole.log(myObject.b); // undefined( "a"  in  myObject ) ; // true ( "b"  in  myObject ) ; // falsemyObject . hasOwnProperty (  "a"  ) ; // true myObject . hasOwnProperty (  "b"  ) ; // false

虽然myObject.amyObject.b都是undefined,但是通过inhasOwnProperty(...)就可以确认数性是否存在于物件当中。

Enumeration

前面我们在介绍属性的特性的时候有稍微介绍什么是enumerable,现在我们来更仔细的介绍一下这个特性。

var myObject = { };Object.defineProperty(myObject,"a",{         value: 2,        enumerable: true,  // make `a` enumerable, as normal    });Object.defineProperty(myObject,"b",{         value: 3        enumerable: false, // make `b` NON-enumerable     });myObject.b; // 3("b" in myObject); // truemyObject.hasOwnProperty( "b" ); // true// .......for (let prop in myObject) {console.log( prop );}// "a"

我们在myObject中建立了两个属性(a与b),我们使用hasOwnProperty(...)确认b是确实存在于myObject中,但是由于他的enumerable设定为false,所以使用for...in无法将它打印出来。

除了使用for...in测试数性是否为enumerable,也可以使用propertyIsEnumerable(..)直接测试这个属性是否存在于物件中并且他的enumerable为何。

var myObject = { };Object.defineProperty(myObject,"a",{         value: 2,        enumerable: true,  // make `a` enumerable, as normal    });Object.defineProperty(myObject,"b",{         value: 3        enumerable: false, // make `b` NON-enumerable     });myObject.propertyIsEnumerable( "a" ); // truemyObject.propertyIsEnumerable( "b" ); // falsemyObject.propertyIsEnumerable( "c" ); // falseObject.keys( myObject ); // ["a"]Object.getOwnPropertyNames( myObject ); // ["a", "b"]

也可以使用Object.keys(..)他会返回所有enumerable = ture的属性。


Iteration

在阵列中迭代所有值得方法是使用标準的for loop

var myArray = [1, 2, 3];for (let i = 0; i < myArray.length; i++) {console.log( myArray[i] );}// 1 2 3

上面的程式中并不是迭代了阵列中的值,而是迭代了阵列的index,而我们透过迭代获得的index取得阵列中对应的数(muyArray[i])。

在ES5中提供了一些对于阵列的迭代方法,forEach(...)every(...)some(...)

forEach(...)会迭代阵列中的所有值并将这些值分别带入callback function中every(...)会持续迭代到最后或着return一个false。some(...)会持续迭代到最后或着return一个true

如果想直接得到迭代值而不是阵列的index,在ES6中提供了一个新语法for...of它可以用来迭代阵列或物件。

const myArray = [1, 2, 3];for(let val of myArray){    console.log(val);}// 1// 2// 3

for...of被要求要迭代的东西需要提供一个迭代器物件,每一次循环中都会调用这个迭代器物件的next(...) method 。

在阵列中拥有内建的迭代器物件(@@iterator)所以可以轻易地使用for...of进行迭代,我们也可手动建立一个@@interator来看看他是如何运作的。

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 }

迭代器的next(...) return 一组物件{value: ... , done: ...},物件中的value属性代表当前迭代着值而done则代表是否迭代完成。


结论

本章节中我们介绍了物件更多的特质,属性的特性、如何将物件变为不可变、物件中的取值与赋值等等,我们来做一些总结:

物件中的数性都有三种特性:writable:控制这个属性是否能被更改。Configurable:控制这个属性是否能更改特性配置。Enumerable:控制这个属性是否能被迭代。当我们需要建立一个不可变的物件时,可以透过以下办法达到:将属性的 writable与configurable 设定为false,让这个属性不能被更改。使用Object.preventExtensions(...)可以防止物件添加新属性。使用Object.seal(...)可以防止添加新属性并且将数性标记为configurable:false(即不可更改属性设置),但是可以更改现有的属性的值。使用Object.freeze(...)可以建立最高级别的不可变物件,即不可新增属性也不可更改现有属性。当访问物件中的属性则会调用[[Get]],会在物件中找到这个属性的名字,若找到则回传相对应的值,若没有则回传undefined。当设定物件的属性会调用[[Put]]:当属性是setter则调用setter。当这个属性的writable = false,在严格模式下掷出TypeError否则无声的失败。不符合上面两点则正常赋值。Getter 是用来取得指定属性的值的方法而Setter是用来设定指定属性的值的方法。可以使用inhasOwnProperty(...)检查属性是否存在于物件中。可以使用propertyIsEnumerable(...)测试数性是否存在于物件中别且检查这个属性使否是可迭代的。可以使用Object.keys(...)他将会返回物件中所有可迭代的属性。可以使用for...of迭代阵列中的值。for...of迭代实际上是执行阵列中内建@@iteratornext(...)

参考文献:You Don't Know JavaScript


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章