Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

重学js —— 赋值运算符(包括解构赋值)和 逗号运算符 #77

Open
lizhongzhen11 opened this issue Feb 26, 2020 · 0 comments
Labels
js基础 Good for newcomers 重学js 重学js系列 规范+MDN

Comments

@lizhongzhen11
Copy link
Owner

lizhongzhen11 commented Feb 26, 2020

赋值运算符(包括解构赋值)逗号运算符

赋值运算符

  • =
  • *=
  • /=
  • %=
  • +=
  • -=
  • <<=
  • >>=
  • >>>=
  • &=
  • ^=
  • |=
  • **=
// 来自 高级前端面试
var a = {n: 1}
var b = a // 此时b和a指向同一个对象
a.x = a = {n: 2} 

// a.x = a = {n: 2} 其实可以看成 b.x = a = {n: 2} 
// a.x 会给 a和b 当前共同指向的对象增加个 x 属性,此时属性值为 undefined
a.x // undefined 即 b.x 

// 接下来是赋值,由于右边依然是个赋值语句,
// 此时应该先拿到 a = {n: 2} 的赋值结果,然后将该结果在赋值给 b.x
// 由于a指向改变,所以此时和b指向的不是同一个对象,
// 但是b指向的对象其 x 属性指向a,所以b指向的对象的x会改变
{
  n: 1,
  x: {
    n: 2
  }
}

console.log(a.x) // undefined
console.log(b.x) // {n: 2}

运行时语义:求值

赋值表达式 : 左侧表达式 = 赋值表达式

  1. 如果 左侧表达式 既不是 对象字面量 也不是 数组字面量
    1. 定义 lref左侧表达式 求值结果
    2. ReturnIfAbrupt(lref)
    3. 如果 赋值表达式 是匿名函数并且 左侧表达式IsIdentifierReftrue
      1. 定义 rval 为带有参数 GetReferencedName(lref)的 赋值表达式NamedEvaluation
    4. 否则,
      1. 定义 rref赋值表达式 的求值结果
      2. 定义 rval? GetValue(rref)
    5. 执行 ? PutValue(lref, rval)
    6. 返回 rval
  2. 定义 assignmentPattern 为被 左侧表达式 覆盖的 分配模式
  3. 定义 rref 赋值表达式 的求值结果
  4. 定义 rval? GetValue(rref)
  5. 执行 使用 rval 作为参数的 assignmentPattern ? 分解赋值计算
  6. 返回 rval

赋值表达式 : 左侧表达式 赋值运算符 赋值表达式

  1. 定义 lref左侧表达式 求值结果
  2. 定义 lval? GetValue(lref)
  3. 定义 rref赋值表达式 的求值结果
  4. 定义 rval? GetValue(rref)
  5. 当赋值运算符是 @= 时,定义 op@ (这里的 @ 是符号替代表示)
  6. 定义 r 为将 op 应用于 lvalrval 的结果,就像 lval op rval 表达式一样 (PS:以 “+” 为例,其实就是 lval + rval
  7. 执行 ? PutValue(lref, r)
  8. 返回 r

当在 严格模式 中出现赋值时,如果在第一个算法的第一步或者第二个算法的第7步中 lref 是一个未解决的引用,则会引起运行时错误。会抛出 ReferenceError 异常。左侧表达式 不能是一个 { [[Writable]]: false }数据属性 引用,不能是一个 { [[Set]]: undefined }访问器属性 引用,也不能是对一个不可扩展对象(该对象的 IsExtensible 返回 false)的引用。如果出现以上情况,会抛出 TypeError 异常。

解构赋值

解构赋值求值

伴随参数 value

对象赋值模式 : { }

  1. 执行 ? RequireObjectCoercible(value)
  2. 返回 NormalCompletion(empty)

对象赋值模式 :

  { 赋值属性列表 }

  { 赋值属性列表, }

  1. 执行 ? RequireObjectCoercible(value)
  2. 执行使用 value 作为参数的 赋值属性列表? PropertyDestructuringAssignmentEvaluation
  3. 返回 NormalCompletion(empty)

数组赋值模式 : [ ]

这里应该是普通的数组赋值

[] = [1]
  1. 定义 iteratorRecord? GetIterator(value)
  2. 返回 ? IteratorClose(iteratorRecord, NormalCompletion(empty))

数组赋值模式 : [ 忽略 ]

[, , ] = [1, 2, 3];
  1. 定义 iteratorRecord? GetIterator(value)
  2. 定义 result 为 使用参数 iteratorRecord忽略IteratorDestructuringAssignmentEvaluation
  3. 如果 iteratorRecord.[[Done]]false,返回 ? IteratorClose(iteratorRecord, result)
  4. 返回 result

数组赋值模式 : [ 忽略opt 赋值剩余元素 ]

[, a] = [1, 2];
console.log(a); // 2
  1. 定义 iteratorRecord? GetIterator(value)
  2. 如果 忽略 存在,
    1. 定义 status 为 使用参数 iteratorRecord忽略IteratorDestructuringAssignmentEvaluation
    2. 如果 statusabrupt completion
      1. 断言:iteratorRecord.[[Done]]true
      2. 返回 Completion(status)
  3. 定义 result 为使用参数 iteratorRecord赋值剩余元素IteratorDestructuringAssignmentEvaluation
  4. 如果 iteratorRecord.[[Done]]false,返回 ? IteratorClose(iteratorRecord, result)
  5. 返回 result

数组赋值模式 : [ 赋值元素列表 ]

[a, b, c] = [1, 2, 3];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
  1. 定义 iteratorRecord? GetIterator(value)
  2. 定义 result 为使用参数 iteratorRecord赋值元素列表IteratorDestructuringAssignmentEvaluation
  3. 如果 iteratorRecord.[[Done]]false,返回 ? IteratorClose(iteratorRecord, result)
  4. 返回 result

数组赋值模式 : [ 赋值元素列表, 忽略opt 赋值剩余元素opt ]

[a, b, , ...c] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(b); // 2
console.log(c); // [4, 5]
  1. 定义 iteratorRecord? GetIterator(value)
  2. 定义 status 为 使用参数 iteratorRecord赋值元素列表IteratorDestructuringAssignmentEvaluation
  3. 如果 statusabrupt completion
    1. 如果 iteratorRecord.[[Done]]false,返回 ? IteratorClose(iteratorRecord, status)
    2. 返回 Completion(status)
  4. 如果 忽略 存在,
    1. 设置 status 为 使用 iteratorRecord 作为参数执行 忽略IteratorDestructuringAssignmentEvaluation 的结果
    2. 如果 statusabrupt completion
      1. 断言:iteratorRecord.[[Done]]true
      2. 返回 Completion(status)
  5. 如果 赋值剩余元素 存在,
    1. 设置 status 为 使用 iteratorRecord 作为参数执行 赋值剩余元素IteratorDestructuringAssignmentEvaluation 的结果
  6. 如果 iteratorRecord.[[Done]]false,返回 ? IteratorClose(iteratorRecord, status)
  7. 返回 Completion(status)

对象赋值模式 : { 赋值剩余属性 }

({...rest} = {a: 1, b: 2});
console.log(rest); // {a: 1, b: 2}
  1. 执行 ? RequireObjectCoercible(value)
  2. 定义 excludedNames 为一个新的空 List
  3. 返回 使用 valueexcludedNames 做参数的 赋值剩余属性RestDestructuringAssignmentEvaluation 执行结果

对象赋值模式 : { 赋值属性列表, 赋值剩余属性 }

({a, ...rest} = {a: 1, b: 2, c: 3});
console.log(a); // 1
console.log(rest); // {b: 2, c: 3}
  1. 执行 ? RequireObjectCoercible(value)
  2. 定义 excludedNames 为 使用参数 value赋值属性列表RestDestructuringAssignmentEvaluation 执行结果
  3. 返回 使用参数 valueexcludedNames赋值剩余属性RestDestructuringAssignmentEvaluation 执行结果

PropertyDestructuringAssignmentEvaluation

伴有参数value

注意:以下操作收集所有已解构的属性名称的列表。

赋值属性列表 : 赋值属性列表, 赋值属性

var o = {p: 42, q: true};
var {p, q} = o;

console.log(p); // 42
console.log(q); // true
  1. 定义 propertyNames 为 使用参数 value赋值属性列表? PropertyDestructuringAssignmentEvaluation
  2. 定义 nextNames 为 使用参数 value赋值属性? PropertyDestructuringAssignmentEvaluation
  3. nextNames 中的每项添加到 propertyNames 的尾部
  4. 返回 propertyNames

赋值属性 : 标识符引用 初始化器opt

var a, b;
({a, b} = {a: 1, b: 2});
console.log(a); // 1
console.log(b); // 2

var o = {p: 42, q: true};
var {p: foo, q: bar} = o;
 
console.log(foo); // 42 
console.log(bar); // true 
  1. 定义 P标识符引用 的字符串值
  2. 定义 lref? ResolveBinding(P)
  3. 定义 v? GetV(value, P)
  4. 如果 初始化器opt 存在且 vundefined
    1. 如果 初始化器 是匿名函数,
      1. 设置 v 为 执行带参数 P初始化器NamedEvaluation
    2. 否则,
      1. 定义 defaultValue初始化器 求值结果
      2. 设置 v? GetValue(defaultValue)
  5. 执行 ? PutValue(lref, v)
  6. 返回一个新的包含 PList

赋值属性 : 属性名 赋值元素

let key = "z";
let { [key]: foo } = { z: "bar" };

console.log(foo); // "bar"
  1. 定义 name属性名 求值结果
  2. ReturnIfAbrupt(name)
  3. 执行 使用 valuename 作为参数的 ? KeyedDestructuringAssignmentEvaluation
  4. 返回一个新的包含 name赋值元素List

RestDestructuringAssignmentEvaluation

伴有参数 valueexcludedNames

let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}
a; // 10 
b; // 20 
rest; // { c: 30, d: 40 }

赋值剩余属性 : ... 解构赋值目标对象

  1. 定义 lref解构赋值目标对象 的求值结果
  2. ReturnIfAbrupt(lref)
  3. 定义 restObjOrdinaryObjectCreate(%Object.prototype%)
  4. 执行 ? CopyDataProperties(restObj, value, excludedNames)
  5. 返回 PutValue(lref, restObj)

IteratorDestructuringAssignmentEvaluation

比较长,省略。。。

KeyedDestructuringAssignmentEvaluation

伴有参数 valuepropertyName

let key = "z";
let { [key]: foo } = { z: "bar" };

console.log(foo); // "bar"

赋值元素 : 解构赋值目标对象 初始化器opt

  1. 如果 解构赋值目标对象 既不是 对象字面量 也不是 数组字面量
    1. 定义 lref解构赋值目标对象 的求值结果
    2. ReturnIfAbrupt(lref)
  2. 定义 v? GetV(value, propertyName)
  3. 如果 初始化器opt 存在且 vundefined
    1. 如果 初始化器 是匿名函数且 解构赋值目标对象IsIdentifierRef 也为 true
      1. 定义 rhsValue 为 带有参数 GetReferencedName(lref) 的 初始化器 的 NamedEvaluation
    2. 否则
      1. 定义 defaultValue初始化器 的求值结果
      2. 定义 rhsValue? GetValue(defaultValue)
  4. 否则,定义 rhsValuev
  5. 如果 解构赋值目标对象对象字面量 或者 数组字面量
    1. 定义 assignmentPattern 为被 解构赋值目标对象 覆盖赋值模式
    2. 返回 参数为 rhsValueassignmentPattern解构赋值求值 结果
  6. 返回 ? PutValue(lref, rhsValue)

2020-03-20 补充

// 来自高级前端面试
const myFunc = ({x, y, z}) => {
  console.log(x, y, z)
}
myFunc(1, 2, 3) // undefined undefined undefined

A. 1 2 3
B. {1: 1} {2: 2} {3: 3}
C. {1: undefined} undefined undefined
D. undefined undefined undefined

这题不难,但是我容易迷糊,说到底是基础不够牢。

2020-03-26 补充

// 来自高级前端面试,我选错了
const spookyItems = [1, 2, 3];
({item: spookyItems[3]} = {item: 4});
console.log(spookyItems) // 选B

A. [1, 2, 3]
B. [1, 2, 3, 4]
C. [1, 2, 3, {item: 4}]
D. [1, 2, 3, "[object Object]"]

我知道会拿到 4 这个值,但没想到会把它放进数组里!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
js基础 Good for newcomers 重学js 重学js系列 规范+MDN
Projects
None yet
Development

No branches or pull requests

1 participant