<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ECMAScript 6 入門教程—對(duì)象的擴(kuò)展

          共 9507字,需瀏覽 20分鐘

           ·

          2020-09-17 23:14

          作者 | 阮一峰


          對(duì)象(object)是 JavaScript 最重要的數(shù)據(jù)結(jié)構(gòu)。ES6 對(duì)它進(jìn)行了重大升級(jí),本章介紹數(shù)據(jù)結(jié)構(gòu)本身的改變,下一章介紹Object對(duì)象的新增方法。

          1、屬性的簡(jiǎn)潔表示法

          ES6 允許在大括號(hào)里面,直接寫入變量和函數(shù),作為對(duì)象的屬性和方法。這樣的書寫更加簡(jiǎn)潔。
          const foo = 'bar';
          const baz = {foo};
          baz // {foo: "bar"}

          // 等同于
          const baz = {foo: foo};
          上面代碼中,變量foo直接寫在大括號(hào)里面。這時(shí),屬性名就是變量名, 屬性值就是變量值。下面是另一個(gè)例子。
          function f(x, y) {
          return {x, y};
          }

          // 等同于

          function f(x, y) {
          return {x: x, y: y};
          }

          f(1, 2) // Object {x: 1, y: 2}
          除了屬性簡(jiǎn)寫,方法也可以簡(jiǎn)寫。
          const o = {
          method() {
          return "Hello!";
          }
          };

          // 等同于

          const o = {
          method: function() {
          return "Hello!";
          }
          };
          下面是一個(gè)實(shí)際的例子。
          let birth = '2000/01/01';

          const Person = {

          name: '張三',

          //等同于birth: birth
          birth,

          // 等同于hello: function ()...
          hello() { console.log('我的名字是', this.name); }

          };
          這種寫法用于函數(shù)的返回值,將會(huì)非常方便。
          function getPoint() {
          const x = 1;
          const y = 10;
          return {x, y};
          }

          getPoint()
          // {x:1, y:10}
          CommonJS 模塊輸出一組變量,就非常合適使用簡(jiǎn)潔寫法。
          let ms = {};

          function getItem (key) {
          return key in ms ? ms[key] : null;
          }

          function setItem (key, value) {
          ms[key] = value;
          }

          function clear () {
          ms = {};
          }

          module.exports = { getItem, setItem, clear };
          // 等同于
          module.exports = {
          getItem: getItem,
          setItem: setItem,
          clear: clear
          };
          屬性的賦值器(setter)和取值器(getter),事實(shí)上也是采用這種寫法。
          const cart = {
          _wheels: 4,

          get wheels () {
          return this._wheels;
          },

          set wheels (value) {
          if (value < this._wheels) {
          throw new Error('數(shù)值太小了!');
          }
          this._wheels = value;
          }
          }
          簡(jiǎn)潔寫法在打印對(duì)象時(shí)也很有用。
          let user = {
          name: 'test'
          };

          let foo = {
          bar: 'baz'
          };

          console.log(user, foo)
          // {name: "test"} {bar: "baz"}
          console.log({user, foo})
          // {user: {name: "test"}, foo: {bar: "baz"}}
          上面代碼中,console.log直接輸出user和foo兩個(gè)對(duì)象時(shí),就是兩組鍵值對(duì),可能會(huì)混淆。把它們放在大括號(hào)里面輸出,就變成了對(duì)象的簡(jiǎn)潔表示法,每組鍵值對(duì)前面會(huì)打印對(duì)象名,這樣就比較清晰了。
          注意,簡(jiǎn)寫的對(duì)象方法不能用作構(gòu)造函數(shù),會(huì)報(bào)錯(cuò)。
          const obj = {
          f() {
          this.foo = 'bar';
          }
          };

          new obj.f() // 報(bào)錯(cuò)
          上面代碼中,f是一個(gè)簡(jiǎn)寫的對(duì)象方法,所以obj.f不能當(dāng)作構(gòu)造函數(shù)使用。

          2、屬性名表達(dá)式

          JavaScript 定義對(duì)象的屬性,有兩種方法。
          // 方法一
          obj.foo = true;

          // 方法二
          obj['a' + 'bc'] = 123;
          上面代碼的方法一是直接用標(biāo)識(shí)符作為屬性名,方法二是用表達(dá)式作為屬性名,這時(shí)要將表達(dá)式放在方括號(hào)之內(nèi)。
          但是,如果使用字面量方式定義對(duì)象(使用大括號(hào)),在 ES5 中只能使用方法一(標(biāo)識(shí)符)定義屬性。
          var obj = {
          foo: true,
          abc: 123
          };
          ES6 允許字面量定義對(duì)象時(shí),用方法二(表達(dá)式)作為對(duì)象的屬性名,即把表達(dá)式放在方括號(hào)內(nèi)。
          let propKey = 'foo';

          let obj = {
          [propKey]: true,
          ['a' + 'bc']: 123
          };
          下面是另一個(gè)例子。
          let lastWord = 'last word';

          const a = {
          'first word': 'hello',
          [lastWord]: 'world'
          };

          a['first word'] // "hello"
          a[lastWord] // "world"
          a['last word'] // "world"
          表達(dá)式還可以用于定義方法名。
          let obj = {
          ['h' + 'ello']() {
          return 'hi';
          }
          };

          obj.hello() // hi
          注意,屬性名表達(dá)式與簡(jiǎn)潔表示法,不能同時(shí)使用,會(huì)報(bào)錯(cuò)。
          // 報(bào)錯(cuò)
          const foo = 'bar';
          const bar = 'abc';
          const baz = { [foo] };

          // 正確
          const foo = 'bar';
          const baz = { [foo]: 'abc'};
          注意,屬性名表達(dá)式如果是一個(gè)對(duì)象,默認(rèn)情況下會(huì)自動(dòng)將對(duì)象轉(zhuǎn)為字符串[object Object],這一點(diǎn)要特別小心。
          const keyA = {a: 1};
          const keyB = {b: 2};

          const myObject = {
          [keyA]: 'valueA',
          [keyB]: 'valueB'
          };

          myObject // Object {[object Object]: "valueB"}
          上面代碼中,[keyA]和[keyB]得到的都是[object Object],所以[keyB]會(huì)把[keyA]覆蓋掉,而myObject最后只有一個(gè)[object Object]屬性。

          3、方法的 name 屬性

          函數(shù)的name屬性,返回函數(shù)名。對(duì)象方法也是函數(shù),因此也有name屬性。
          const person = {
          sayName() {
          console.log('hello!');
          },
          };

          person.sayName.name // "sayName"
          上面代碼中,方法的name屬性返回函數(shù)名(即方法名)。
          如果對(duì)象的方法使用了取值函數(shù)(getter)和存值函數(shù)(setter),則name屬性不是在該方法上面,而是該方法的屬性的描述對(duì)象的get和set屬性上面,返回值是方法名前加上get和set。
          const obj = {
          get foo() {},
          set foo(x) {}
          };

          obj.foo.name
          // TypeError: Cannot read property 'name' of undefined

          const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

          descriptor.get.name // "get foo"
          descriptor.set.name // "set foo"
          有兩種特殊情況:bind方法創(chuàng)造的函數(shù),name屬性返回bound加上原函數(shù)的名字;Function構(gòu)造函數(shù)創(chuàng)造的函數(shù),name屬性返回anonymous。
          (new Function()).name // "anonymous"

          var doSomething = function() {
          // ...
          };
          doSomething.bind().name // "bound doSomething"
          如果對(duì)象的方法是一個(gè) Symbol 值,那么name屬性返回的是這個(gè) Symbol 值的描述。
          const key1 = Symbol('description');
          const key2 = Symbol();
          let obj = {
          [key1]() {},
          [key2]() {},
          };
          obj[key1].name // "[description]"
          obj[key2].name // ""
          上面代碼中,key1對(duì)應(yīng)的 Symbol 值有描述,key2沒有。

          4、屬性的可枚舉性和遍歷

          可枚舉性

          對(duì)象的每個(gè)屬性都有一個(gè)描述對(duì)象(Descriptor),用來(lái)控制該屬性的行為。Object.getOwnPropertyDescriptor方法可以獲取該屬性的描述對(duì)象。
          let obj = { foo: 123 };
          Object.getOwnPropertyDescriptor(obj, 'foo')
          // {
          // value: 123,
          // writable: true,
          // enumerable: true,
          // configurable: true
          // }
          描述對(duì)象的enumerable屬性,稱為“可枚舉性”,如果該屬性為false,就表示某些操作會(huì)忽略當(dāng)前屬性。
          目前,有四個(gè)操作會(huì)忽略enumerable為false的屬性。
          • for...in循環(huán):只遍歷對(duì)象自身的和繼承的可枚舉的屬性。

          • Object.keys():返回對(duì)象自身的所有可枚舉的屬性的鍵名。

          • JSON.stringify():只串行化對(duì)象自身的可枚舉的屬性。

          • Object.assign(): 忽略enumerable為false的屬性,只拷貝對(duì)象自身的可枚舉的屬性。

          這四個(gè)操作之中,前三個(gè)是 ES5 就有的,最后一個(gè)Object.assign()是 ES6 新增的。其中,只有for...in會(huì)返回繼承的屬性,其他三個(gè)方法都會(huì)忽略繼承的屬性,只處理對(duì)象自身的屬性。實(shí)際上,引入“可枚舉”(enumerable)這個(gè)概念的最初目的,就是讓某些屬性可以規(guī)避掉for...in操作,不然所有內(nèi)部屬性和方法都會(huì)被遍歷到。比如,對(duì)象原型的toString方法,以及數(shù)組的length屬性,就通過(guò)“可枚舉性”,從而避免被for...in遍歷到。
          Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
          // false

          Object.getOwnPropertyDescriptor([], 'length').enumerable
          // false
          上面代碼中,toString和length屬性的enumerable都是false,因此for...in不會(huì)遍歷到這兩個(gè)繼承自原型的屬性。
          另外,ES6 規(guī)定,所有 Class 的原型的方法都是不可枚舉的。
          Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
          // false
          總的來(lái)說(shuō),操作中引入繼承的屬性會(huì)讓問(wèn)題復(fù)雜化,大多數(shù)時(shí)候,我們只關(guān)心對(duì)象自身的屬性。所以,盡量不要用for...in循環(huán),而用Object.keys()代替。

          屬性的遍歷

          ES6 一共有 5 種方法可以遍歷對(duì)象的屬性。
          (1)for...in
          for...in循環(huán)遍歷對(duì)象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)。
          (2)Object.keys(obj)
          Object.keys返回一個(gè)數(shù)組,包括對(duì)象自身的(不含繼承的)所有可枚舉屬性(不含 Symbol 屬性)的鍵名。
          (3)Object.getOwnPropertyNames(obj)
          Object.getOwnPropertyNames返回一個(gè)數(shù)組,包含對(duì)象自身的所有屬性(不含 Symbol 屬性,但是包括不可枚舉屬性)的鍵名。
          (4)Object.getOwnPropertySymbols(obj)
          Object.getOwnPropertySymbols返回一個(gè)數(shù)組,包含對(duì)象自身的所有 Symbol 屬性的鍵名。
          (5)Reflect.ownKeys(obj)
          Reflect.ownKeys返回一個(gè)數(shù)組,包含對(duì)象自身的(不含繼承的)所有鍵名,不管鍵名是 Symbol 或字符串,也不管是否可枚舉。
          以上的 5 種方法遍歷對(duì)象的鍵名,都遵守同樣的屬性遍歷的次序規(guī)則。
          • 首先遍歷所有數(shù)值鍵,按照數(shù)值升序排列。

          • 其次遍歷所有字符串鍵,按照加入時(shí)間升序排列。

          • 最后遍歷所有 Symbol 鍵,按照加入時(shí)間升序排列。

          Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
          // ['2', '10', 'b', 'a', Symbol()]
          上面代碼中,Reflect.ownKeys方法返回一個(gè)數(shù)組,包含了參數(shù)對(duì)象的所有屬性。這個(gè)數(shù)組的屬性次序是這樣的,首先是數(shù)值屬性2和10,其次是字符串屬性b和a,最后是 Symbol 屬性。

          5、super 關(guān)鍵字

          我們知道,this關(guān)鍵字總是指向函數(shù)所在的當(dāng)前對(duì)象,ES6 又新增了另一個(gè)類似的關(guān)鍵字super,指向當(dāng)前對(duì)象的原型對(duì)象。
          const proto = {
          foo: 'hello'
          };

          const obj = {
          foo: 'world',
          find() {
          return super.foo;
          }
          };

          Object.setPrototypeOf(obj, proto);
          obj.find() // "hello"
          上面代碼中,對(duì)象obj.find()方法之中,通過(guò)super.foo引用了原型對(duì)象proto的foo屬性。
          注意,super關(guān)鍵字表示原型對(duì)象時(shí),只能用在對(duì)象的方法之中,用在其他地方都會(huì)報(bào)錯(cuò)。
          // 報(bào)錯(cuò)
          const obj = {
          foo: super.foo
          }

          // 報(bào)錯(cuò)
          const obj = {
          foo: () => super.foo
          }

          // 報(bào)錯(cuò)
          const obj = {
          foo: function () {
          return super.foo
          }
          }
          上面三種super的用法都會(huì)報(bào)錯(cuò),因?yàn)閷?duì)于 JavaScript 引擎來(lái)說(shuō),這里的super都沒有用在對(duì)象的方法之中。
          第一種寫法是super用在屬性里面,第二種和第三種寫法是super用在一個(gè)函數(shù)里面,然后賦值給foo屬性。
          目前,只有對(duì)象方法的簡(jiǎn)寫法可以讓 JavaScript 引擎確認(rèn),定義的是對(duì)象的方法。
          JavaScript 引擎內(nèi)部,super.foo等同于Object.getPrototypeOf(this).foo(屬性)或Object.getPrototypeOf(this).foo.call(this)(方法)。
          const proto = {
          x: 'hello',
          foo() {
          console.log(this.x);
          },
          };

          const obj = {
          x: 'world',
          foo() {
          super.foo();
          }
          }

          Object.setPrototypeOf(obj, proto);

          obj.foo() // "world"
          上面代碼中,super.foo指向原型對(duì)象proto的foo方法,但是綁定的this卻還是當(dāng)前對(duì)象obj,因此輸出的就是world。

          6、對(duì)象的擴(kuò)展運(yùn)算符

          《數(shù)組的擴(kuò)展》一章中,已經(jīng)介紹過(guò)擴(kuò)展運(yùn)算符(...)。ES2018 將這個(gè)運(yùn)算符引入了對(duì)象。

          解構(gòu)賦值

          對(duì)象的解構(gòu)賦值用于從一個(gè)對(duì)象取值,相當(dāng)于將目標(biāo)對(duì)象自身的所有可遍歷的(enumerable)、但尚未被讀取的屬性,分配到指定的對(duì)象上面。所有的鍵和它們的值,都會(huì)拷貝到新對(duì)象上面。
          let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
          x // 1
          y // 2
          z // { a: 3, b: 4 }
          上面代碼中,變量z是解構(gòu)賦值所在的對(duì)象。它獲取等號(hào)右邊的所有尚未讀取的鍵(a和b),將它們連同值一起拷貝過(guò)來(lái)。
          由于解構(gòu)賦值要求等號(hào)右邊是一個(gè)對(duì)象,所以如果等號(hào)右邊是undefined或null,就會(huì)報(bào)錯(cuò),因?yàn)樗鼈儫o(wú)法轉(zhuǎn)為對(duì)象。
          let { ...z } = null; // 運(yùn)行時(shí)錯(cuò)誤
          let { ...z } = undefined; // 運(yùn)行時(shí)錯(cuò)誤
          解構(gòu)賦值必須是最后一個(gè)參數(shù),否則會(huì)報(bào)錯(cuò)。
          let { ...x, y, z } = someObject; // 句法錯(cuò)誤
          let { x, ...y, ...z } = someObject; // 句法錯(cuò)誤
          上面代碼中,解構(gòu)賦值不是最后一個(gè)參數(shù),所以會(huì)報(bào)錯(cuò)。
          注意,解構(gòu)賦值的拷貝是淺拷貝,即如果一個(gè)鍵的值是復(fù)合類型的值(數(shù)組、對(duì)象、函數(shù))、那么解構(gòu)賦值拷貝的是這個(gè)值的引用,而不是這個(gè)值的副本。
          let obj = { a: { b: 1 } };
          let { ...x } = obj;
          obj.a.b = 2;
          x.a.b // 2
          上面代碼中,x是解構(gòu)賦值所在的對(duì)象,拷貝了對(duì)象obj的a屬性。a屬性引用了一個(gè)對(duì)象,修改這個(gè)對(duì)象的值,會(huì)影響到解構(gòu)賦值對(duì)它的引用。
          另外,擴(kuò)展運(yùn)算符的解構(gòu)賦值,不能復(fù)制繼承自原型對(duì)象的屬性。
          let o1 = { a: 1 };
          let o2 = { b: 2 };
          o2.__proto__ = o1;
          let { ...o3 } = o2;
          o3 // { b: 2 }
          o3.a // undefined
          上面代碼中,對(duì)象o3復(fù)制了o2,但是只復(fù)制了o2自身的屬性,沒有復(fù)制它的原型對(duì)象o1的屬性。
          下面是另一個(gè)例子。
          const o = Object.create({ x: 1, y: 2 });
          o.z = 3;

          let { x, ...newObj } = o;
          let { y, z } = newObj;
          x // 1
          y // undefined
          z // 3
          上面代碼中,變量x是單純的解構(gòu)賦值,所以可以讀取對(duì)象o繼承的屬性;變量y和z是擴(kuò)展運(yùn)算符的解構(gòu)賦值,只能讀取對(duì)象o自身的屬性,所以變量z可以賦值成功,變量y取不到值。
          ES6 規(guī)定,變量聲明語(yǔ)句之中,如果使用解構(gòu)賦值,擴(kuò)展運(yùn)算符后面必須是一個(gè)變量名,而不能是一個(gè)解構(gòu)賦值表達(dá)式,所以上面代碼引入了中間變量newObj,如果寫成下面這樣會(huì)報(bào)錯(cuò)。
          let { x, ...{ y, z } } = o;
          // SyntaxError: ... must be followed by an identifier in declaration contexts
          解構(gòu)賦值的一個(gè)用處,是擴(kuò)展某個(gè)函數(shù)的參數(shù),引入其他操作。
          function baseFunction({ a, b }) {
          // ...
          }
          function wrapperFunction({ x, y, ...restConfig }) {
          // 使用 x 和 y 參數(shù)進(jìn)行操作
          // 其余參數(shù)傳給原始函數(shù)
          return baseFunction(restConfig);
          }
          上面代碼中,原始函數(shù)baseFunction接受a和b作為參數(shù),函數(shù)wrapperFunction在baseFunction的基礎(chǔ)上進(jìn)行了擴(kuò)展,能夠接受多余的參數(shù),并且保留原始函數(shù)的行為。

          擴(kuò)展運(yùn)算符

          對(duì)象的擴(kuò)展運(yùn)算符(...)用于取出參數(shù)對(duì)象的所有可遍歷屬性,拷貝到當(dāng)前對(duì)象之中。
          let z = { a: 3, b: 4 };
          let n = { ...z };
          n // { a: 3, b: 4 }
          由于數(shù)組是特殊的對(duì)象,所以對(duì)象的擴(kuò)展運(yùn)算符也可以用于數(shù)組。
          let foo = { ...['a', 'b', 'c'] };
          foo
          // {0: "a", 1: "b", 2: "c"}
          如果擴(kuò)展運(yùn)算符后面是一個(gè)空對(duì)象,則沒有任何效果。
          {...{}, a: 1}
          // { a: 1 }
          如果擴(kuò)展運(yùn)算符后面不是對(duì)象,則會(huì)自動(dòng)將其轉(zhuǎn)為對(duì)象。
          // 等同于 {...Object(1)}
          {...1} // {}
          上面代碼中,擴(kuò)展運(yùn)算符后面是整數(shù)1,會(huì)自動(dòng)轉(zhuǎn)為數(shù)值的包裝對(duì)象Number{1}。由于該對(duì)象沒有自身屬性,所以返回一個(gè)空對(duì)象。
          下面的例子都是類似的道理。
          // 等同于 {...Object(true)}
          {...true} // {}

          // 等同于 {...Object(undefined)}
          {...undefined} // {}

          // 等同于 {...Object(null)}
          {...null} // {}
          但是,如果擴(kuò)展運(yùn)算符后面是字符串,它會(huì)自動(dòng)轉(zhuǎn)成一個(gè)類似數(shù)組的對(duì)象,因此返回的不是空對(duì)象。
          {...'hello'}
          // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
          對(duì)象的擴(kuò)展運(yùn)算符等同于使用Object.assign()方法。
          let aClone = { ...a };
          // 等同于
          let aClone = Object.assign({}, a);
          上面的例子只是拷貝了對(duì)象實(shí)例的屬性,如果想完整克隆一個(gè)對(duì)象,還拷貝對(duì)象原型的屬性,可以采用下面的寫法。
          // 寫法一
          const clone1 = {
          __proto__: Object.getPrototypeOf(obj),
          ...obj
          };

          // 寫法二
          const clone2 = Object.assign(
          Object.create(Object.getPrototypeOf(obj)),
          obj
          );

          // 寫法三
          const clone3 = Object.create(
          Object.getPrototypeOf(obj),
          Object.getOwnPropertyDescriptors(obj)
          )
          上面代碼中,寫法一的__proto__屬性在非瀏覽器的環(huán)境不一定部署,因此推薦使用寫法二和寫法三。
          擴(kuò)展運(yùn)算符可以用于合并兩個(gè)對(duì)象。
          let ab = { ...a, ...b };
          // 等同于
          let ab = Object.assign({}, a, b);
          如果用戶自定義的屬性,放在擴(kuò)展運(yùn)算符后面,則擴(kuò)展運(yùn)算符內(nèi)部的同名屬性會(huì)被覆蓋掉。
          let aWithOverrides = { ...a, x: 1, y: 2 };
          // 等同于
          let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
          // 等同于
          let x = 1, y = 2, aWithOverrides = { ...a, x, y };
          // 等同于
          let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
          上面代碼中,a對(duì)象的x屬性和y屬性,拷貝到新對(duì)象后會(huì)被覆蓋掉。
          這用來(lái)修改現(xiàn)有對(duì)象部分的屬性就很方便了。
          let newVersion = {
          ...previousVersion,
          name: 'New Name' // Override the name property
          };
          上面代碼中,newVersion對(duì)象自定義了name屬性,其他屬性全部復(fù)制自previousVersion對(duì)象。
          如果把自定義屬性放在擴(kuò)展運(yùn)算符前面,就變成了設(shè)置新對(duì)象的默認(rèn)屬性值。
          let aWithDefaults = { x: 1, y: 2, ...a };
          // 等同于
          let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
          // 等同于
          let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);
          與數(shù)組的擴(kuò)展運(yùn)算符一樣,對(duì)象的擴(kuò)展運(yùn)算符后面可以跟表達(dá)式。
          const obj = {
          ...(x > 1 ? {a: 1} : {}),
          b: 2,
          };
          擴(kuò)展運(yùn)算符的參數(shù)對(duì)象之中,如果有取值函數(shù)get,這個(gè)函數(shù)是會(huì)執(zhí)行的。
          let a = {
          get x() {
          throw new Error('not throw yet');
          }
          }

          let aWithXGetter = { ...a }; // 報(bào)錯(cuò)
          上面例子中,取值函數(shù)get在擴(kuò)展a對(duì)象時(shí)會(huì)自動(dòng)執(zhí)行,導(dǎo)致報(bào)錯(cuò)。

          7、鏈判斷運(yùn)算符

          編程實(shí)務(wù)中,如果讀取對(duì)象內(nèi)部的某個(gè)屬性,往往需要判斷一下該對(duì)象是否存在。比如,要讀取message.body.user.firstName,安全的寫法是寫成下面這樣。
          // 錯(cuò)誤的寫法
          const firstName = message.body.user.firstName;

          // 正確的寫法
          const firstName = (message
          && message.body
          && message.body.user
          && message.body.user.firstName) || 'default';
          上面例子中,firstName屬性在對(duì)象的第四層,所以需要判斷四次,每一層是否有值。
          三元運(yùn)算符?:也常用于判斷對(duì)象是否存在。
          const fooInput = myForm.querySelector('input[name=foo]')
          const fooValue = fooInput ? fooInput.value : undefined
          上面例子中,必須先判斷fooInput是否存在,才能讀取fooInput.value。
          這樣的層層判斷非常麻煩,因此?ES2020?引入了“鏈判斷運(yùn)算符”(optional chaining operator)?.,簡(jiǎn)化上面的寫法。
          const firstName = message?.body?.user?.firstName || 'default';
          const fooValue = myForm.querySelector('input[name=foo]')?.value
          上面代碼使用了?.運(yùn)算符,直接在鏈?zhǔn)秸{(diào)用的時(shí)候判斷,左側(cè)的對(duì)象是否為null或undefined。如果是的,就不再往下運(yùn)算,而是返回undefined。
          下面是判斷對(duì)象方法是否存在,如果存在就立即執(zhí)行的例子。
          iterator.return?.()
          上面代碼中,iterator.return如果有定義,就會(huì)調(diào)用該方法,否則iterator.return直接返回undefined,不再執(zhí)行?.后面的部分。
          對(duì)于那些可能沒有實(shí)現(xiàn)的方法,這個(gè)運(yùn)算符尤其有用。
          if (myForm.checkValidity?.() === false) {
          // 表單校驗(yàn)失敗
          return;
          }
          上面代碼中,老式瀏覽器的表單可能沒有checkValidity這個(gè)方法,這時(shí)?.運(yùn)算符就會(huì)返回undefined,判斷語(yǔ)句就變成了undefined === false,所以就會(huì)跳過(guò)下面的代碼。
          鏈判斷運(yùn)算符有三種用法。
          • obj?.prop?// 對(duì)象屬性

          • obj?.[expr]?// 同上

          • func?.(...args)?// 函數(shù)或?qū)ο蠓椒ǖ恼{(diào)用

          下面是obj?.[expr]用法的一個(gè)例子。
          let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1];
          上面例子中,字符串的match()方法,如果沒有發(fā)現(xiàn)匹配會(huì)返回null,如果發(fā)現(xiàn)匹配會(huì)返回一個(gè)數(shù)組,?.運(yùn)算符起到了判斷作用。
          下面是?.運(yùn)算符常見形式,以及不使用該運(yùn)算符時(shí)的等價(jià)形式。
          a?.b
          // 等同于
          a == null ? undefined : a.b

          a?.[x]
          // 等同于
          a == null ? undefined : a[x]

          a?.b()
          // 等同于
          a == null ? undefined : a.b()

          a?.()
          // 等同于
          a == null ? undefined : a()
          上面代碼中,特別注意后兩種形式,如果a?.b()里面的a.b不是函數(shù),不可調(diào)用,那么a?.b()是會(huì)報(bào)錯(cuò)的。a?.()也是如此,如果a不是null或undefined,但也不是函數(shù),那么a?.()會(huì)報(bào)錯(cuò)。
          使用這個(gè)運(yùn)算符,有幾個(gè)注意點(diǎn)。
          (1)短路機(jī)制
          ?.運(yùn)算符相當(dāng)于一種短路機(jī)制,只要不滿足條件,就不再往下執(zhí)行。
          a?.[++x]
          // 等同于
          a == null ? undefined : a[++x]
          上面代碼中,如果a是undefined或null,那么x不會(huì)進(jìn)行遞增運(yùn)算。也就是說(shuō),鏈判斷運(yùn)算符一旦為真,右側(cè)的表達(dá)式就不再求值。
          (2)delete 運(yùn)算符
          delete a?.b
          // 等同于
          a == null ? undefined : delete a.b
          上面代碼中,如果a是undefined或null,會(huì)直接返回undefined,而不會(huì)進(jìn)行delete運(yùn)算。
          (3)括號(hào)的影響
          如果屬性鏈有圓括號(hào),鏈判斷運(yùn)算符對(duì)圓括號(hào)外部沒有影響,只對(duì)圓括號(hào)內(nèi)部有影響。
          (a?.b).c
          // 等價(jià)于
          (a == null ? undefined : a.b).c
          上面代碼中,?.對(duì)圓括號(hào)外部沒有影響,不管a對(duì)象是否存在,圓括號(hào)后面的.c總是會(huì)執(zhí)行。
          一般來(lái)說(shuō),使用?.運(yùn)算符的場(chǎng)合,不應(yīng)該使用圓括號(hào)。
          (4)報(bào)錯(cuò)場(chǎng)合
          以下寫法是禁止的,會(huì)報(bào)錯(cuò)。
          // 構(gòu)造函數(shù)
          new a?.()
          new a?.b()

          // 鏈判斷運(yùn)算符的右側(cè)有模板字符串
          a?.`{b}`
          a?.b`{c}`

          // 鏈判斷運(yùn)算符的左側(cè)是 super
          super?.()
          super?.foo

          // 鏈運(yùn)算符用于賦值運(yùn)算符左側(cè)
          a?.b = c
          (5)右側(cè)不得為十進(jìn)制數(shù)值
          為了保證兼容以前的代碼,允許foo?.3:0被解析成foo ? .3 : 0,因此規(guī)定如果?.后面緊跟一個(gè)十進(jìn)制數(shù)字,那么?.不再被看成是一個(gè)完整的運(yùn)算符,而會(huì)按照三元運(yùn)算符進(jìn)行處理,也就是說(shuō),那個(gè)小數(shù)點(diǎn)會(huì)歸屬于后面的十進(jìn)制數(shù)字,形成一個(gè)小數(shù)。

          8、Null 判斷運(yùn)算符

          讀取對(duì)象屬性的時(shí)候,如果某個(gè)屬性的值是null或undefined,有時(shí)候需要為它們指定默認(rèn)值。常見做法是通過(guò)||運(yùn)算符指定默認(rèn)值。
          const headerText = response.settings.headerText || 'Hello, world!';
          const animationDuration = response.settings.animationDuration || 300;
          const showSplashScreen = response.settings.showSplashScreen || true;
          上面的三行代碼都通過(guò)||運(yùn)算符指定默認(rèn)值,但是這樣寫是錯(cuò)的。開發(fā)者的原意是,只要屬性的值為null或undefined,默認(rèn)值就會(huì)生效,但是屬性的值如果為空字符串或false或0,默認(rèn)值也會(huì)生效。
          為了避免這種情況,ES2020?引入了一個(gè)新的 Null 判斷運(yùn)算符??。它的行為類似||,但是只有運(yùn)算符左側(cè)的值為null或undefined時(shí),才會(huì)返回右側(cè)的值。
          const headerText = response.settings.headerText ?? 'Hello, world!';
          const animationDuration = response.settings.animationDuration ?? 300;
          const showSplashScreen = response.settings.showSplashScreen ?? true;
          上面代碼中,默認(rèn)值只有在左側(cè)屬性值為null或undefined時(shí),才會(huì)生效。
          這個(gè)運(yùn)算符的一個(gè)目的,就是跟鏈判斷運(yùn)算符?.配合使用,為null或undefined的值設(shè)置默認(rèn)值。
          const animationDuration = response.settings?.animationDuration ?? 300;
          上面代碼中,如果response.settings是null或undefined,或者response.settings.animationDuration是null或undefined,就會(huì)返回默認(rèn)值300。也就是說(shuō),這一行代碼包括了兩級(jí)屬性的判斷。
          這個(gè)運(yùn)算符很適合判斷函數(shù)參數(shù)是否賦值。
          function Component(props) {
          const enable = props.enabled ?? true;
          // …
          }
          上面代碼判斷props參數(shù)的enabled屬性是否賦值,基本等同于下面的寫法。
          function Component(props) {
          const {
          enabled: enable = true,
          } = props;
          // …
          }
          ??有一個(gè)運(yùn)算優(yōu)先級(jí)問(wèn)題,它與&&和||的優(yōu)先級(jí)孰高孰低。現(xiàn)在的規(guī)則是,如果多個(gè)邏輯運(yùn)算符一起使用,必須用括號(hào)表明優(yōu)先級(jí),否則會(huì)報(bào)錯(cuò)。
          // 報(bào)錯(cuò)
          lhs && middle ?? rhs
          lhs ?? middle && rhs
          lhs || middle ?? rhs
          lhs ?? middle || rhs
          上面四個(gè)表達(dá)式都會(huì)報(bào)錯(cuò),必須加入表明優(yōu)先級(jí)的括號(hào)。
          (lhs && middle) ?? rhs;
          lhs && (middle ?? rhs);

          (lhs ?? middle) && rhs;
          lhs ?? (middle && rhs);

          (lhs || middle) ?? rhs;
          lhs || (middle ?? rhs);

          (lhs ?? middle) || rhs;
          lhs ?? (middle || rhs);
          本文完~

          推薦閱讀
          ECMAScript 6 入門教程—數(shù)組的擴(kuò)展
          ECMAScript 6 入門教程—函數(shù)的擴(kuò)展
          ECMAScript 6 入門教程—數(shù)值的擴(kuò)展
          ECMAScript 6 入門教程—正則的擴(kuò)展
          ECMAScript 6 入門教程—字符串的新增方法

          推薦閱讀圖書



          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩狠狠| 中国亚州精品历史女人久久 | 日韩激情片 | 免费看又色又爽又黄的成人用品 | 日韩v欧美v |