ES6 對象的擴(kuò)展

2022-04-01 17:37 更新

1. 屬性的簡潔表示法

ES6 允許在大括號里面,直接寫入變量和函數(shù),作為對象的屬性和方法。這樣的書寫更加簡潔。

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}


// 等同于
const baz = {foo: foo};

上面代碼中,變量 foo 直接寫在大括號里面。這時,屬性名就是變量名, 屬性值就是變量值。下面是另一個例子。

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


// 等同于


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


f(1, 2) // Object {x: 1, y: 2}

除了屬性簡寫,方法也可以簡寫。

const o = {
  method() {
    return "Hello!";
  }
};


// 等同于


const o = {
  method: function() {
    return "Hello!";
  }
};

下面是一個實際的例子。

let birth = '2000/01/01';


const Person = {


  name: '張三',


  //等同于birth: birth
  birth,


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


};

這種寫法用于函數(shù)的返回值,將會非常方便。

function getPoint() {
  const x = 1;
  const y = 10;
  return {x, y};
}


getPoint()
// {x:1, y:10}

CommonJS 模塊輸出一組變量,就非常合適使用簡潔寫法。

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),事實上也是采用這種寫法。

const cart = {
  _wheels: 4,


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


  set wheels (value) {
    if (value < this._wheels) {
      throw new Error('數(shù)值太小了!');
    }
    this._wheels = value;
  }
}

簡潔寫法在打印對象時也很有用。

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òu)造函數(shù),會報錯。

const obj = {
  f() {
    this.foo = 'bar';
  }
};


new obj.f() // 報錯

上面代碼中, f 是一個簡寫的對象方法,所以 obj.f 不能當(dāng)作構(gòu)造函數(shù)使用。

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

JavaScript 定義對象的屬性,有兩種方法。

// 方法一
obj.foo = true;


// 方法二
obj['a' + 'bc'] = 123;

上面代碼的方法一是直接用標(biāo)識符作為屬性名,方法二是用表達(dá)式作為屬性名,這時要將表達(dá)式放在方括號之內(nèi)。

但是,如果使用字面量方式定義對象(使用大括號),在 ES5 中只能使用方法一(標(biāo)識符)定義屬性。

var obj = {
  foo: true,
  abc: 123
};

ES6 允許字面量定義對象時,用方法二(表達(dá)式)作為對象的屬性名,即把表達(dá)式放在方括號內(nèi)。

let propKey = 'foo';


let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

下面是另一個例子。

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á)式與簡潔表示法,不能同時使用,會報錯。

// 報錯
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };


// 正確
const foo = 'bar';
const baz = { [foo]: 'abc'};

注意,屬性名表達(dá)式如果是一個對象,默認(rèn)情況下會自動將對象轉(zhuǎn)為字符串 [object Object] ,這一點要特別小心。

const keyA = {a: 1};
const keyB = {b: 2};


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


myObject // Object {[object Object]: "valueB"}

上面代碼中, [keyA] 和 [keyB] 得到的都是 [object Object] ,所以 [keyB] 會把 [keyA] 覆蓋掉,而 myObject 最后只有一個 [object Object] 屬性。

3. 方法的 name 屬性

函數(shù)的name屬性,返回函數(shù)名。對象方法也是函數(shù),因此也有 name 屬性。

const person = {
  sayName() {
    console.log('hello!');
  },
};


person.sayName.name   // "sayName"

上面代碼中,方法的 name 屬性返回函數(shù)名(即方法名)。

如果對象的方法使用了取值函數(shù)( getter )和存值函數(shù)( setter ),則 name 屬性不是在該方法上面,而是該方法的屬性的描述對象的 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"

如果對象的方法是一個 Symbol 值,那么 name 屬性返回的是這個 Symbol 值的描述。

const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
  [key1]() {},
  [key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""

上面代碼中, key1 對應(yīng)的 Symbol 值有描述, key2 沒有。

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

可枚舉性

對象的每個屬性都有一個描述對象(Descriptor),用來控制該屬性的行為。 Object.getOwnPropertyDescriptor方法可以獲取該屬性的描述對象。

let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }

描述對象的 enumerable 屬性,稱為“可枚舉性”,如果該屬性為 false ,就表示某些操作會忽略當(dāng)前屬性。

目前,有四個操作會忽略 enumerable 為 false 的屬性。

  • for...in 循環(huán):只遍歷對象自身的和繼承的可枚舉的屬性。
  • Object.keys() :返回對象自身的所有可枚舉的屬性的鍵名。
  • JSON.stringify() :只串行化對象自身的可枚舉的屬性。
  • Object.assign() : 忽略 enumerable 為 false 的屬性,只拷貝對象自身的可枚舉的屬性。

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

Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false


Object.getOwnPropertyDescriptor([], 'length').enumerable
// false

上面代碼中, toString 和 length 屬性的 enumerable 都是 false ,因此 for...in 不會遍歷到這兩個繼承自原型的屬性。

另外,ES6 規(guī)定,所有 Class 的原型的方法都是不可枚舉的。

Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false

總的來說,操作中引入繼承的屬性會讓問題復(fù)雜化,大多數(shù)時候,我們只關(guān)心對象自身的屬性。所以,盡量不要用 for...in 循環(huán),而用 Object.keys() 代替。

屬性的遍歷

ES6 一共有5 種方法可以遍歷對象的屬性。

(1)for...in

for...in 循環(huán)遍歷對象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)。

(2)Object.keys(obj)

Object.keys 返回一個數(shù)組,包括對象自身的(不含繼承的)所有可枚舉屬性(不含 Symbol 屬性)的鍵名。

(3)Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames 返回一個數(shù)組,包含對象自身的所有屬性(不含 Symbol 屬性,但是包括不可枚舉屬性)的鍵名。

(4)Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols 返回一個數(shù)組,包含對象自身的所有 Symbol 屬性的鍵名。

(5)Reflect.ownKeys(obj)

Reflect.ownKeys 返回一個數(shù)組,包含對象自身的(不含繼承的)所有鍵名,不管鍵名是 Symbol 或字符串,也不管是否可枚舉。

以上的 5 種方法遍歷對象的鍵名,都遵守同樣的屬性遍歷的次序規(guī)則。

  • 首先遍歷所有數(shù)值鍵,按照數(shù)值升序排列。
  • 其次遍歷所有字符串鍵,按照加入時間升序排列。
  • 最后遍歷所有 Symbol 鍵,按照加入時間升序排列。

Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]

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

5. super 關(guān)鍵字

我們知道, this關(guān)鍵字總是指向函數(shù)所在的當(dāng)前對象,ES6 又新增了另一個類似的關(guān)鍵字 super ,指向當(dāng)前對象的原型對象。

const proto = {
  foo: 'hello'
};


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


Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

上面代碼中,對象 obj.find()方法之中,通過 super.foo引用了原型對象protofoo 屬性。

注意, super 關(guān)鍵字表示原型對象時,只能用在對象的方法之中,用在其他地方都會報錯。

// 報錯
const obj = {
  foo: super.foo
}


// 報錯
const obj = {
  foo: () => super.foo
}


// 報錯
const obj = {
  foo: function () {
    return super.foo
  }
}

上面三種 super 的用法都會報錯,因為對于 JavaScript 引擎來說,這里的 super 都沒有用在對象的方法之中。第一種寫法是 super 用在屬性里面,第二種和第三種寫法是 super 用在一個函數(shù)里面,然后賦值給 foo 屬性。目前,只有對象方法的簡寫法可以讓 JavaScript 引擎確認(rèn),定義的是對象的方法。

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 指向原型對象 proto 的 foo 方法,但是綁定的 this 卻還是當(dāng)前對象 obj ,因此輸出的就是 world 。

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

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

解構(gòu)賦值

對象的解構(gòu)賦值用于從一個對象取值,相當(dāng)于將目標(biāo)對象自身的所有可遍歷的(enumerable)、但尚未被讀取的屬性,分配到指定的對象上面。所有的鍵和它們的值,都會拷貝到新對象上面。

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

上面代碼中,變量 z 是解構(gòu)賦值所在的對象。它獲取等號右邊的所有尚未讀取的鍵( a 和 b ),將它們連同值一起拷貝過來。

由于解構(gòu)賦值要求等號右邊是一個對象,所以如果等號右邊是 undefined 或 null ,就會報錯,因為它們無法轉(zhuǎn)為對象。

let { ...z } = null; // 運(yùn)行時錯誤
let { ...z } = undefined; // 運(yùn)行時錯誤

解構(gòu)賦值必須是最后一個參數(shù),否則會報錯。

let { ...x, y, z } = someObject; // 句法錯誤
let { x, ...y, ...z } = someObject; // 句法錯誤

上面代碼中,解構(gòu)賦值不是最后一個參數(shù),所以會報錯。

注意,解構(gòu)賦值的拷貝是淺拷貝,即如果一個鍵的值是復(fù)合類型的值(數(shù)組、對象、函數(shù))、那么解構(gòu)賦值拷貝的是這個值的引用,而不是這個值的副本。

let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

上面代碼中, x 是解構(gòu)賦值所在的對象,拷貝了對象 obj 的 a 屬性。 a 屬性引用了一個對象,修改這個對象的值,會影響到解構(gòu)賦值對它的引用。

另外,擴(kuò)展運(yùn)算符的解構(gòu)賦值,不能復(fù)制繼承自原型對象的屬性。

let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined

上面代碼中,對象 o3 復(fù)制了 o2 ,但是只復(fù)制了 o2 自身的屬性,沒有復(fù)制它的原型對象 o1 的屬性。

下面是另一個例子。

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)賦值,所以可以讀取對象 o 繼承的屬性;變量 y 和 z 是擴(kuò)展運(yùn)算符的解構(gòu)賦值,只能讀取對象 o 自身的屬性,所以變量 z 可以賦值成功,變量 y 取不到值。ES6 規(guī)定,變量聲明語句之中,如果使用解構(gòu)賦值,擴(kuò)展運(yùn)算符后面必須是一個變量名,而不能是一個解構(gòu)賦值表達(dá)式,所以上面代碼引入了中間變量 newObj ,如果寫成下面這樣會報錯。

let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts

解構(gòu)賦值的一個用處,是擴(kuò)展某個函數(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)算符

對象的擴(kuò)展運(yùn)算符( ... )用于取出參數(shù)對象的所有可遍歷屬性,拷貝到當(dāng)前對象之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

由于數(shù)組是特殊的對象,所以對象的擴(kuò)展運(yùn)算符也可以用于數(shù)組。

let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}

如果擴(kuò)展運(yùn)算符后面是一個空對象,則沒有任何效果。

{...{}, a: 1}
// { a: 1 }

如果擴(kuò)展運(yùn)算符后面不是對象,則會自動將其轉(zhuǎn)為對象。

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

上面代碼中,擴(kuò)展運(yùn)算符后面是整數(shù) 1 ,會自動轉(zhuǎn)為數(shù)值的包裝對象 Number{1} 。由于該對象沒有自身屬性,所以返回一個空對象。

下面的例子都是類似的道理。

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


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


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

但是,如果擴(kuò)展運(yùn)算符后面是字符串,它會自動轉(zhuǎn)成一個類似數(shù)組的對象,因此返回的不是空對象。

{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}

對象的擴(kuò)展運(yùn)算符等同于使用 Object.assign() 方法。

let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);

上面的例子只是拷貝了對象實例的屬性,如果想完整克隆一個對象,還拷貝對象原型的屬性,可以采用下面的寫法。

// 寫法一
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)算符可以用于合并兩個對象。

let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

如果用戶自定義的屬性,放在擴(kuò)展運(yùn)算符后面,則擴(kuò)展運(yùn)算符內(nèi)部的同名屬性會被覆蓋掉。

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 對象的 x 屬性和 y 屬性,拷貝到新對象后會被覆蓋掉。

這用來修改現(xiàn)有對象部分的屬性就很方便了。

let newVersion = {
  ...previousVersion,
  name: 'New Name' // Override the name property
};

上面代碼中, newVersion 對象自定義了 name 屬性,其他屬性全部復(fù)制自 previousVersion 對象。

如果把自定義屬性放在擴(kuò)展運(yùn)算符前面,就變成了設(shè)置新對象的默認(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)算符一樣,對象的擴(kuò)展運(yùn)算符后面可以跟表達(dá)式。

const obj = {
  ...(x > 1 ? {a: 1} : {}),
  b: 2,
};

擴(kuò)展運(yùn)算符的參數(shù)對象之中,如果有取值函數(shù) get ,這個函數(shù)是會執(zhí)行的。

let a = {
  get x() {
    throw new Error('not throw yet');
  }
}


let aWithXGetter = { ...a }; // 報錯

上面例子中,取值函數(shù) get 在擴(kuò)展 a 對象時會自動執(zhí)行,導(dǎo)致報錯。

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

在實際編程中,如果讀取對象內(nèi)部的某個屬性,往往需要判斷一下該對象是否存在。比如,要讀取 message.body.user.firstName ,安全的寫法是寫成下面這樣。

const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';

或者使用三元運(yùn)算符 ?: ,判斷一個對象是否存在。

const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined

這樣的層層判斷非常麻煩,因此 ES2020 引入了“鏈判斷運(yùn)算符”(optional chaining operator) ?. ,簡化上面的寫法。

const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value

上面代碼使用了 ?. 運(yùn)算符,直接在鏈?zhǔn)秸{(diào)用的時候判斷,左側(cè)的對象是否為 null 或 undefined 。如果是的,就不再往下運(yùn)算,而是返回 undefined 。

鏈判斷運(yùn)算符有三種用法。

  • obj?.prop // 對象屬性
  • obj?.[expr] // 同上
  • func?.(...args) // 函數(shù)或?qū)ο蠓椒ǖ恼{(diào)用

下面是判斷對象方法是否存在,如果存在就立即執(zhí)行的例子。

iterator.return?.()

上面代碼中,iterator.return如果有定義,就會調(diào)用該方法,否則直接返回 undefined 。

對于那些可能沒有實現(xiàn)的方法,這個運(yùn)算符尤其有用。

if (myForm.checkValidity?.() === false) {
  // 表單校驗失敗
  return;
}

上面代碼中,老式瀏覽器的表單可能沒有 checkValidity 這個方法,這時 ?. 運(yùn)算符就會返回 undefined ,判斷語句就變成了 undefined === false ,所以就會跳過下面的代碼。

下面是這個運(yùn)算符常見的使用形式,以及不使用該運(yùn)算符時的等價形式。

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() 是會報錯的。 a?.() 也是如此,如果 a 不是 null 或 undefined ,但也不是函數(shù),那么 a?.() 會報錯。

使用這個運(yùn)算符,有幾個注意點。

(1)短路機(jī)制

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

上面代碼中,如果 a 是 undefined 或 null ,那么 x 不會進(jìn)行遞增運(yùn)算。也就是說,鏈判斷運(yùn)算符一旦為真,右側(cè)的表達(dá)式就不再求值。

(2)delete 運(yùn)算符

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

上面代碼中,如果 a 是 undefined 或 null ,會直接返回 undefined ,而不會進(jìn)行 delete 運(yùn)算。

(3)括號的影響

如果屬性鏈有圓括號,鏈判斷運(yùn)算符對圓括號外部沒有影響,只對圓括號內(nèi)部有影響。

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

上面代碼中, ?. 對圓括號外部沒有影響,不管 a 對象是否存在,圓括號后面的 .c 總是會執(zhí)行。

一般來說,使用 ?. 運(yùn)算符的場合,不應(yīng)該使用圓括號。

(4)報錯場合

以下寫法是禁止的,會報錯。

// 構(gòu)造函數(shù)
new a?.()
new a?.b()


// 鏈判斷運(yùn)算符的右側(cè)有模板字符串
a?.``
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ī)定如果 ?. 后面緊跟一個十進(jìn)制數(shù)字,那么 ?. 不再被看成是一個完整的運(yùn)算符,而會按照三元運(yùn)算符進(jìn)行處理,也就是說,那個小數(shù)點會歸屬于后面的十進(jìn)制數(shù)字,形成一個小數(shù)。

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

讀取對象屬性的時候,如果某個屬性的值是 null 或 undefined ,有時候需要為它們指定默認(rèn)值。常見做法是通過 || 運(yùn)算符指定默認(rèn)值。

const headerText = response.settings.headerText || 'Hello, world!';
const animationDuration = response.settings.animationDuration || 300;
const showSplashScreen = response.settings.showSplashScreen || true;

上面的三行代碼都通過 || 運(yùn)算符指定默認(rèn)值,但是這樣寫是錯的。開發(fā)者的原意是,只要屬性的值為 null 或 undefined ,默認(rèn)值就會生效,但是屬性的值如果為空字符串或 false 或 0 ,默認(rèn)值也會生效。

為了避免這種情況,ES2020 引入了一個新的 Null 判斷運(yùn)算符 ?? 。它的行為類似 || ,但是只有運(yùn)算符左側(cè)的值為 null 或 undefined 時,才會返回右側(cè)的值。

const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;

上面代碼中,默認(rèn)值只有在屬性值為 null 或 undefined 時,才會生效。

這個運(yùn)算符的一個目的,就是跟鏈判斷運(yùn)算符 ?. 配合使用,為 null 或 undefined 的值設(shè)置默認(rèn)值。

const animationDuration = response.settings?.animationDuration ?? 300;

上面代碼中, response.settings 如果是 null 或 undefined ,就會返回默認(rèn)值300。

這個運(yùn)算符很適合判斷函數(shù)參數(shù)是否賦值。

function Component(props) {
  const enable = props.enabled ?? true;
  // …
}

上面代碼判斷 props 參數(shù)的 enabled 屬性是否賦值,等同于下面的寫法。

function Component(props) {
  const {
    enabled: enable = true,
  } = props;
  // …
}

?? 有一個運(yùn)算優(yōu)先級問題,它與 && 和 || 的優(yōu)先級孰高孰低?,F(xiàn)在的規(guī)則是,如果多個邏輯運(yùn)算符一起使用,必須用括號表明優(yōu)先級,否則會報錯。

// 報錯
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs

上面四個表達(dá)式都會報錯,必須加入表明優(yōu)先級的括號。


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


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


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


(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);
```ES6 允許在大括號里面,直接寫入變量和函數(shù),作為對象的屬性和方法。這樣的書寫更加簡潔。
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號