gzl的博客

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

RN-《你不知道的JS》上卷第二部分

发表于 2019-08-03 更新于 2020-02-06 分类于 读书笔记

第一章:关于 this

下面是一段关于 this 经典误解的代码

1
2
3
4
5
6
7
8
9
10
function foo() {
var a = 2;
this.bar();
}

function bar() {
console.log(this.a) // undefined
}

foo();

第二章:this 全面理解

绑定规则

默认绑定

规则

在非严格模式下,进行独立函数调用像下面这样,this 默认绑定到了 window 上。

1
2
3
4
5
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2

在严格模式下,则不能将全局对象用于默认绑定,因此 this 会绑定到 undefined,会报错

1
2
3
4
5
6
function foo() {
"use strict"
console.log(this.a);
}
var a = 2;
foo(); // TypeError: Cannot read property 'a' of undefined

这里注意,在严格模式下调用 foo 不影响默认绑定

1
2
3
4
5
6
7
8
function foo() {
console.log(this.a);
}
var a = 2; // 这里不能写在 立即执行函数 后面,因为那时 a === undefined
(function () {
"use strict";
foo(); // 2
})()

间接引用

间接引用最容易在赋值时发生:下面的代码中 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置时 foo() 而不是 p.foo() 或者 o.foo()。这里会应用默认绑定。

1
2
3
4
5
6
7
8
function foo() {
console.log(this.a);
}
var a = 2;
var o = { a: 3, foo: foo};
var p = { a: 4};
o.foo(); // 3
(p.foo = o.foo)(); // 2

隐式绑定

规则

当函数引用有上下文对象时, 隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。

1
2
3
4
5
6
7
8
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
obj.foo();

对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
console.log(this.a);
}
var obj2 = {
a: 2,
foo: foo
}
// 这里的 obj2 必须放在上面,不然会报错,如果放在下面,obj1 中的 obj2 === undefined
var obj1 = {
a: 1,
obj2: obj2
}
obj1.obj2.foo(); // 2

隐式丢失

隐式丢失的问题:丢失绑定对象,应用默认绑定

  1. 函数别名

下面这段代码中虽然 bar 是 obj.foo 的一个引用,但实际上,它引用的是 foo 函数本身,因此 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

1
2
3
4
5
6
7
8
9
10
11
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名
var a = "Hi, global"

bar() //Hi, global
  1. 函数传参
1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
console.log(this.a);
}
function doFoo(fn) {
fn();
}
var obj = {
a: 2,
foo: foo
}
var a = "global";
doFoo(obj.foo); // global

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值

传入语言内置函数,与上面的结果相同

1
2
3
4
5
6
7
8
9
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
var a = "global";
setTimeout(obj.foo, 100); // global

显式绑定

call、apply、bind

1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
var bar = function () {
foo.call(obj);
}
bar(); // 2
setTimeout(bar, 100); // 2
bar.call(window); // 2

绑定之后就不能再修改 this 的指向。

包裹函数

典型的一个应用场景就是创建一个包裹函数,负责接收参数并返回值

1
2
3
4
5
6
7
8
9
10
11
12
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
var obj = {
a: 2
};
var bar = function () {
return foo.apply(obj, arguments);
}
var b = bar(3); // 2 3
console.log(b); // 5

辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
// 简单的辅助绑定函数
function bind(fn, obj) {
return function () {
return fn.apply(obj, arguments);
}
}
var obj = {
a: 2
};

var bar = bind(foo, obj);

var b = bar(3); // 2 3
console.log(b); // 5

bind

1
2
3
4
5
6
7
8
9
10
11
12
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
var obj = {
a: 2
};

var bar = foo.bind(obj);

var b = bar(3); // 2 3
console.log(b); // 5

new绑定

使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[Prototype]] 连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
1
2
3
4
5
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a) // 2

优先级

可以按照下面的顺序进行判断:

  1. 函数是否在 new 中调用(new绑定)?如果是的话this绑定的是新创建的对象。var bar = new foo()
  2. 函数是否通过call、apply(显式绑定)或者bind(硬绑定)调用。var bar = foo.call(obj2)
  3. 函数是否在某个上下文对象中调用(隐式绑定)。var bar = obj1.foo()
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined ,否则绑定到全局对象。var bar = foo()

绑定例外

如果把 null 或者 undefined 作为 this 的绑定对象传入 call、apply或者bind,这些值在调用时会被忽略,实际上应用的是默认绑定规则。

1
2
3
4
5
function foo() {
console.log(this.a)
}
var a = 2;
foo.call(null);

柯里化

一种非常常见的做法是使用apply()来展开一个数组,并当作参数传入一个函数。类似的,bind() 可以对参数进行柯里化(预先设置一些参数)

1
2
3
4
5
6
7
8
9
10
function foo(a, b) {
console.log(a)
console.log(b);
}
// 把数组展开成参数
foo.apply(null, [2, 3]); // 2 3

// 使用 bind()进行柯里化
var bar = foo.bind(null, 2);
bar(3); // 2 3

这两种方法都需要传入一个参数当做 this 的绑定对象

Object.create(null)

Object.create(null) 和 {} 很像,但是不会创建 Object.prototype 这个委托,所有它比 {} 更空,这样设置比设置为 null 和 undefined 更安全。

1
2
3
4
5
6
7
8
9
10
11
function foo(a, b) {
console.log(a)
console.log(b);
}
// 把数组展开成参数
var k = Object.create(null);
foo.apply(k, [2, 3]); // 2 3

// 使用 bind()进行柯里化
var bar = foo.bind(k, 2);
bar(3); // 2 3

第三章: 对象

属性描述符

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

创建普通属性时属性描述符会使用默认值,我们也可以使用 Object.defineProperty() 来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置。一般来说不会使用。

1
2
3
4
5
6
7
8
9
10
var myObject = {}

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

console.log(myObject.a) // 2

writable

决定是否可以修改属性的值。

1
2
3
4
5
6
7
8
9
10
11
12
var myObject = {}

Object.defineProperty(myObject, "a", {
value: 2,
writable: false, // 不可写!
enumerable: true,
configurable: true
})

myObject.a = 3;

console.log(myObject.a) // 2

在上面的非严格模式下,只是修改静默失败,如果在严格模式下,这种方法会报错。

configurable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var myObject = {}

Object.defineProperty(myObject, "a", {
value: 2,
writable: true,
enumerable: true,
configurable: false // 不可配置
})
myObject.a = 3;

Object.defineProperty(myObject, "a", {
value: 3,
writable: true,
enumerable: true,
configurable: true
})
// TypeError: Cannot redefine property: a at Function.defineProperty (<anonymous>)

可以看到把 configurable 修改为 false 是单向操作,无法撤销!

有一个小小的例外:即便属性是 configurable: false,我们还是可以把 writable 的状态由 true 改为 false ,但是无法由 false 改为 true。

除了无法修改, configurable: false 还会禁止删除这个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var myObject = {
a: 2
}
console.log(myObject.a); // 2
delete myObject.a;
console.log(myObject.a); // undefined

Object.defineProperty(myObject, "a", {
value: 2,
writable: true,
enumerable: true,
configurable: false // 不可配置
})

console.log(myObject.a); // 2
delete myObject.a;
console.log(myObject.a); // 2

enumerable

这个是控制属性是否会出现在对象的属性枚举中,比如 for in 循环,如果把 enumerable 设置为 false,这个属性就不会出现在枚举中,虽然仍然可以正常访问它,设置为 true 就会让它出现在枚举中。

下面的代码可以检验是否为可枚举:

propertyIsEnumerable() 会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足 enumerable:true 。

Object.keys() 会返回一个数组,包含所有可枚举属性,Object.getOwnPropertyNames() 会返回一个数组,包含所有属性,无论它们是否可枚举。这两个方法都只会查找对象直接包含的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var myObject = {}
Object.defineProperty( myObject, "a", {
enumerable: true, // 可枚举
value: 2
})

Object.defineProperty( myObject, "b", {
enumerable: false, // 不可枚举
value: 3
})

myObject.propertyIsEnumerable("a") // true
myObject.propertyIsEnumerable("b") // false

Object.keys(myObject) // ["a"]
Object.getOwnPropertyNames(myObject) // ["a", "b"]

存在性

1
2
3
4
5
var myObject = {
a: undefined
}
console.log(myObject.a) // undefined
console.log(myObject.b) // undefined

可以应用 in 和 hasOwnProperty 来进行判断属性是否真的存在于对象中。

in hasOwnProperty

1
2
3
4
5
6
7
8
var myObject = {
a: 2
}
"a" in myObject // true
"b" in myObject // false

myObject.hasOwnProperty("a") // true
myObject.hasOwnProperty("b") // false

in 操作符会检查属性是否在对象及其 [[Prototype]]原型链 中,hasOwnProperty() 只会检查属性是否在 myObject对象中,不会检查 [[Prototype]]链。

RN-《你不知道的JS》上卷第一部分

发表于 2019-08-03 更新于 2020-02-06 分类于 读书笔记

第一部分 作用域和闭包

第一章 作用域是什么

编译原理

尽管通常将 JavaScript 归类为 “动态” 或 “解释执行” 语言,但事实上它是一门 编译语言,但与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植。

在传统编译语言流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为 “编译”。

分词/词法分析(Tokenizing/Lexing)

这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块 , 这些代码块被称为词法单元(token)。例如 var a = 2 ,被分解成这些词法单元:var、a、=、2、; 空格是否会被当做词法单元,取决于空格在这门语言中是否具有意义。

解析/语法分析(Parsing)

这个过程将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树被称为 “ 抽象语法树 “(Abstract Syntax Tree,AST)。

代码生成

简单来说有某种方法可以将 var = 2 的 AST 转化为一组机器指令,用来创建一个叫做 a 的变量(包括分配内存等),并将一个值储存在 a 中。

与其他语言不同,JavaScript 的编译过程不是发生在构建之前的。大部分的编译发生在代码执行前的几微秒的时间里,比起编译过程只有三步的语言的编译器,JavaScript 引擎要复杂得多。

理解作用域

演员表

  • 引擎:从头到尾负责整个 JavaScript 程序的编译及执行过程

  • 编译器:引擎的好朋友之一,负责语言分析及代码生成等脏活累活

  • 作用域:引擎的另一位好朋友,负责收集并维护由所有声明的标识符(变量)组成的一系列查询

变量赋值

对于 var a = 2 ,引擎认为这里有两个完全不同的声明,一个由编译器在编译时处理,另一个则由引擎在运行时处理。

变量赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

  1. 首先,var a 在其作用域中声明新变量。这会在最开始的阶段,也就是代码执行前进行。

  2. 接下来,a = 2 会查询(LHS 查询)变量 a 并对其进行赋值。

引擎查找

在 var a = 2 ,这个例子中,引擎会为变量 a 进行 LHS 查询,另一个查找的类型叫做 RHS。

简单理解

如果查找的目标是对变量进行赋值,那么就会使用 LHS 查询;如果目的是获取变量的值,就会使用 RHS 查询; 赋值操作符会导致 LHS 查询,= 操作符 或 调用函数时传入参数的操作都会导致关联作用域的赋值操作。

深入理解

RHS查询 与简单查找某个变量的值别无二致, 而 LHS查询 则是试图找到变量的容器本身,从而可以对其赋值。在概念上最好将其理解为 “ 赋值操作的目标是谁(LHS)” 以及 “ 谁是赋值操作的源头(RHS)”。

1
2
3
console.log(a); // 这里对 a 的引用是一个 RHS引用,因为这里 a 并没有赋予任何值。

var a = 2; // 这里对 a 的引用则是 LHS引用,因为实际上我们并不关心当前的值是什么,只是想为 = 2 这个赋值操作找到一个目标
  1. 实例一理解 LHS RHS
1
2
3
4
5
6
7
8
9
10
function foo(a) {
console.log(a);
}
foo(2);

// LHS: 参数 a 的隐式赋值
// RHS:
// 1. console.log(a) 中对 a 的 RHS查询,将得到的 a 的值传给 console.log(..)
// 2. console.log(..) 本身会对 console 对象进行 RHS查询,检查是否有一个叫做 log 的方法
// 3. foo(..) 的调用需要对 foo 进行 RHS查询,意为找到 foo 的值并把它给我
  1. 实例二理解 LHS RHS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);

// LHS:
// 1. 参数 a 的隐式赋值
// 2. var b = a; 中对 b 的 LHS查询
// 3. var c = foo(2); 中对 c 的 LHS查询

// RHS:
// 1. var b = a; 中对 a 的 RHS查询
// 2. return a + b 中对 a、b 的 RHS查询
// 3. var c = foo(2) 中对 foo(..) 的 RHS查询

异常

ReferenceError

不成功的 RHS、LHS(严格)查询

1
2
3
4
5
function foo(a) {
console.log(a + b);
b = a;
}
foo(2);

上面这段代码在对 b 进行 RHS 查询时无法找到,引擎会抛出 ReferenceError : Uncaught ReferenceError: b is not defined。

在非严格模式下,对 a 的 LHS 查询并没有抛出错误,而会在全局创建一个 a 的变量,在严格模式下,LHS 查询没有结果也会抛出 ReferenceError。

TypeError

成功的 RHS 查询,但是是非法的操作

1
2
var b = null;
b();

如果 RHS 查询到了,但是对其进行不合理的操作,比如对非函数类型的值进行函数调用,那么引擎会抛出 TypeError:Uncaught TypeError: b is not a function

小结

  • ReferenceError 同作用域判别失败相关

  • TypeError 代表作用域判别成功了,但对结果的操作是非法的或不合理的。

  • 不成功的 RHS 引用会导致 ReferenceError 异常

  • 不成功的 LHS 引用

    • 非严格模式下会创建全局变量
    • 严格模式下导致 ReferenceError 异常

第二章 词法作用域

简单的说,词法作用域就是定义在词法阶段的作用域

词法阶段

window a 通过这种技术可以访问那些被同名变量所遮蔽的全局变量,但是非全局变量如果被遮蔽了无论如何都无法被访问到。

词法作用域只会查找一级标识符,如果代码中引用了 foo.bar.baz ,词法作用域只会试图查找 foo 标识符,找到这个变量后,对象属性访问规则会接管对 bar 和 baz 属性的访问。

欺骗词法

不要使用 eval 和 with

第三章 函数作用域和块作用域

函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用

函数作用域

从所写的代码中挑选出一个任意的片段,然后用函数声明对它进行包装,然后再用立即执行函数(IIFE)的方式再进行一层包装。

立即执行函数表达式

1
2
3
4
5
6
7
8
9
10
var a = 2;

(function IIFE() {

var a = 3;
console.log(a); //3

})()

console.log(a); // 2

立即执行函数传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 传递 window 对象
var a = 2;

(function IIFE(global) {

var a = 3;
console.log(a); //3
console.log(global.a) // 2

})(window);

// 传递 undefined:将一个参数命名为 undefined,对应位置不传入任何值,这样就可以保证是真的 undefined
undefined = true; // 不要这样做
(function IIFE(undefined) {

var a;
if (a === undefined) {
console.log('undefined is safe here!')
}

})();

IIFE 传函数当做参数

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 2;

(function IIFE(def) {

def(window);

})(function def(global) {

var a = 3;
console.log(a); // 3
console.log(global.a);

});

块作用域

能用 let 和 const 就不用 var

第四章 提升

无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理,可以将这个过程形象地想象成所有的声明(变量和函数),都会被 “移动” 到各自作用域的最顶端,这个过程被称为提升。

声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。

第五章 作用域闭包

定义

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

1
2
3
4
5
6
7
8
9
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar
}
var baz = foo();
baz() // 2 -- 这就是闭包的效果

对于上面的代码,简单来说,bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。

1
2
3
4
5
6
7
8
9
10
11
function foo() {
var a = 2;
function bar() {
console.log(a);
}
baz(bar)
}
function baz(fn) {
fn(); // 2
}
foo()
1
2
3
4
5
6
7
8
9
10
var fn;
function foo() {
var a = 2;
function bar() {
console.log(a);
}
fn = bar
}
foo()
fn() // 2

无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

本质理解

下面这段代码中 timer 保有对变量 message 的引用,wait(…) 执行 1000 毫秒后,它的内部作用域并不会消失,timer 函数依然保有 wait(..) 作用域的闭包。

在引擎内部,内置的工具函数 setTimeout(..) 持有对一个参数的引用,这个参数也许叫 fn 或者 func,或者其它名字。引擎会调用这个函数,在这个例子中就是内部的 timer 函数。

1
2
3
4
5
6
function wait(message) {
setTimeout(function timer() {
console.log(message) // Hello, closure
}, 1000)
}
wait('Hello, closure')

本质上无论何时何地,如果将函数当做第一级的值类型并到处传递,就可以看到闭包在这些函数中的应用。在定时器、事件监听器、Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要用了回调函数,实际上就是在使用闭包!

循环和闭包

普通循环

1
2
3
4
5
for(var i = 1; i <= 5; i ++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000)
}

上面这段代码会一秒打印一个6,总共打印5个6

原因就是延时函数的回调会在循环结束时才执行,当定时器运行时即使每个迭代中执行的是 setTimeout(.., 0),即 0ms 后执行 setTimeout,所有的回调函数依然是在循环结束后才会被执行,因此会每次输出一个 6 出来。

IIFE + 闭包

1
2
3
4
5
6
7
for(var i = 1; i <= 5; i ++) {
(function (i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000)
})(i);
}

块作用域 + 闭包

1
2
3
4
5
for(let i = 1; i <= 5; i ++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000)
}

模块

模块模式

模块模式需要两个必要条件:

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something )
}
function doAnother() {
console.log( another.join(" ! ") );
}
return {
doSomething,
doAnother
}
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

单例模式

当只需要一个实例时,可以对模块模式进行简单的改进来实现单例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var foo = (function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something )
}
function doAnother() {
console.log( another.join(" ! ") );
}
return {
doSomething,
doAnother
}
})()
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

补充

可以传参,另外可以命名将要作为公共API返回的对象,比如下面的 change() 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var foo = (function CoolModule(id) {
function first() {
console.log(id)
}

function second() {
console.log(id.toUpperCase());
}

function change() {
publicAPI.identity = second
}
var publicAPI = {
change,
identity: first
}
return publicAPI;
})(" foo module ")
foo.identity(); // foo module
foo.change();
foo.identity(); // FOO MODULE

Javascript-questions-1

发表于 2019-08-02 更新于 2020-02-24 分类于 JavaScript

https://github.com/lydiahallie/javascript-questions

https://github.com/lydiahallie/javascript-questions/blob/master/README-zh_CN.md

this

3.关于this在普通函数和箭头函数中的指向问题

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * this.radius
};

console.log(shape.diameter());
console.log(shape.perimeter());

// 20 and NaN

Note that the value of diameter is a regular function, whereas the value of perimeter is an arrow function.

With arrow functions, the this keyword refers to its current surrounding scope, unlike regular functions! This means that when we call perimeter, it doesn’t refer to the shape object, but to its surrounding scope (window for example).

There is no value radius on that object, which returns undefined.

class static

8.class中的静态方法static

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor;
return this.newColor;
}

constructor({ newColor = "green" } = {}) {
this.newColor = newColor;
}
}

const freddie = new Chameleon({ newColor: "purple" });
console.log(freddie.colorChange("orange"));

// TypeError

The colorChange function is static. Static methods are designed to live only on the constructor in which they are created, and cannot be passed down to any children. Since freddie is a child, the function is not passed down, and not available on the freddie instance: a TypeError is thrown.

new

12.new的性质

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

const lydia = new Person("Lydia", "Hallie");
const sarah = Person("Sarah", "Smith");

console.log(lydia);
console.log(sarah);

// Person {firstName: "Lydia", lastName: "Hallie"} and undefined

For sarah, we didn’t use the new keyword. When using new, it refers to the new empty object we create. However, if you don’t add new it refers to the global object!

We said that this.firstName equals "Sarah" and this.lastName equals "Smith". What we actually did, is defining global.firstName = 'Sarah' and global.lastName = 'Smith'. sarah itself is left undefined, since we don’t return a value from the Person function.

事件传播

13.事件的执行顺序

Capturing > Target > Bubbling

在捕获(capturing)阶段中,事件从祖先元素向下传播到目标元素。当事件达到目标(target)元素后,冒泡(bubbling)才开始。

img

函数传参

17.传递字符串模板作为函数的参数

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
function getPersonInfo(one, two, three) {
console.log(one);
console.log(two);
console.log(three);
}

const person = "Lydia";
const age = 21;

getPersonInfo`${person} is ${age} years old`;

// ["", " is ", " years old"] "Lydia" 21

If you use tagged template literals, the value of the first argument is always an array of the string values. The remaining arguments get the values of the passed expressions!

sessionStorage

22.sessionStorage的访问时间

How long is cool_secret accessible?

1
sessionStorage.setItem('cool_secret', 123)

The data stored in sessionStorage is removed after closing the tab.

If you used localStorage, the data would’ve been there forever, unless for example localStorage.clear() is invoked.

key type is string?

24.key在不同类型的数据中是什么类型?

What’s the output?

1
2
3
4
5
6
7
8
9
const obj = { 1: "a", 2: "b", 3: "c" };
const set = new Set([1, 2, 3, 4, 5]);

obj.hasOwnProperty("1");
obj.hasOwnProperty(1);
set.has("1");
set.has(1);

// true true false true

All object keys (excluding Symbols) are strings under the hood, even if you don’t type it yourself as a string. This is why obj.hasOwnProperty('1') also returns true.

It doesn’t work that way for a set. There is no '1' in our set: set.has('1') returns false. It has the numeric type 1, set.has(1) returns true.

same key

25.key值相同会发生什么?

What’s the output?

1
2
3
4
const obj = { a: "one", b: "two", a: "three" };
console.log(obj);

// { a: "three", b: "two" }

If you have two keys with the same name, the key will be replaced. It will still be in its first position, but with the last specified value.

key is object

29.对象的key是对象会怎样?

What’s the output?

1
2
3
4
5
6
7
8
9
10
const a = {};
const b = { key: "b" };
const c = { key: "c" };

a[b] = 123;
a[c] = 456;

console.log(a[b]);

// 456

Object keys are automatically converted into strings. We are trying to set an object as a key to object a, with the value of 123.

However, when we stringify an object, it becomes "[Object object]". So what we are saying here, is that a["Object object"] = 123. Then, we can try to do the same again. c is another object that we are implicitly stringifying. So then, a["Object object"] = 456.

Then, we log a[b], which is actually a["Object object"]. We just set that to 456, so it returns 456.

event.target

31.事件对象

What is the event.target when clicking the button?

1
2
3
4
5
6
7
<div onclick="console.log('first div')">
<div onclick="console.log('second div')">
<button onclick="console.log('button')">
Click!
</button>
</div>
</div>

event.target 是 button

The deepest nested element that caused the event is the target of the event. You can stop bubbling by event.stopPropagation

打印顺序:button second div first div

在事件传播期间,有三个阶段:捕获、目标和冒泡。默认情况下,事件处理程序在冒泡阶段执行(除非将 useCapture 设置为 true)。它从嵌套最深的元素向外传播。

try catch

38.try catch 的作用域块

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
(() => {
let x, y;
try {
throw new Error();
} catch (x) {
(x = 1), (y = 2);
console.log(x);
}
console.log(x);
console.log(y);
})();

// 1 undefined 2

The catch block receives the argument x. This is not the same x as the variable when we pass arguments. This variable xis block-scoped.

Later, we set this block-scoped variable equal to 1, and set the value of the variable y. Now, we log the block-scoped variable x, which is equal to 1.

Outside of the catch block, x is still undefined, and y is 2. When we want to console.log(x) outside of the catchblock, it returns undefined, and y returns 2.

reduce

40.reduce的用法

What’s the output?

1
2
3
4
5
6
7
8
[[0, 1], [2, 3]].reduce(
(acc, cur) => {
return acc.concat(cur);
},
[1, 2]
);

// [1, 2, 0, 1, 2, 3]

[1, 2] is our initial value. This is the value we start with, and the value of the very first acc. During the first round, acc is [1,2], and cur is [0, 1]. We concatenate them, which results in [1, 2, 0, 1].

Then, [1, 2, 0, 1] is acc and [2, 3] is cur. We concatenate them, and get [1, 2, 0, 1, 2, 3]

generator yield

44.generator 和 yield 的用法

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
function* generator(i) {
yield i;
yield i * 2;
}

const gen = generator(10);

console.log(gen.next().value);
console.log(gen.next().value);

// 10 20

Regular functions cannot be stopped mid-way after invocation. However, a generator function can be “stopped” midway, and later continue from where it stopped. Every time a generator function encounters a yield keyword, the function yields the value specified after it. Note that the generator function in that case doesn’t return the value, it yields the value.

First, we initialize the generator function with i equal to 10. We invoke the generator function using the next() method. The first time we invoke the generator function, i is equal to 10. It encounters the first yield keyword: it yields the value of i. The generator is now “paused”, and 10 gets logged.

Then, we invoke the function again with the next() method. It starts to continue where it stopped previously, still with iequal to 10. Now, it encounters the next yield keyword, and yields i * 2. i is equal to 10, so it returns 10 * 2, which is 20. This results in 10, 20.

Promise.race

45.Promise.race 的用法

What does this return?

1
2
3
4
5
6
7
8
9
10
11
const firstPromise = new Promise((res, rej) => {
setTimeout(res, 500, "one");
});

const secondPromise = new Promise((res, rej) => {
setTimeout(res, 100, "two");
});

Promise.race([firstPromise, secondPromise]).then(res => console.log(res));

// "two"

When we pass multiple promises to the Promise.race method, it resolves/rejects the first promise that resolves/rejects. To the setTimeout method, we pass a timer: 500ms for the first promise (firstPromise), and 100ms for the second promise (secondPromise). This means that the secondPromise resolves first with the value of 'two'. res now holds the value of 'two', which gets logged.

reference

46.又是一个关于引用的问题,挺有意思的

What’s the output?

1
2
3
4
5
6
7
let person = { name: "Lydia" };
const members = [person];
person = null;

console.log(members);

// [{ name: "Lydia" }]

https://github.com/lydiahallie/javascript-questions#46-whats-the-output

parseInt

49.parseInt的用法

What’s the value of num?

1
2
3
4
const num = parseInt("7*6", 10);
console.log(num)

// 7

Only the first numbers in the string is returned. Based on the radix (the second argument in order to specify what type of number we want to parse it to: base 10, hexadecimal, octal, binary, etc.), the parseInt checks whether the characters in the string are valid. Once it encounters a character that isn’t a valid number in the radix, it stops parsing and ignores the following characters.

* is not a valid number. It only parses "7" into the decimal 7. num now holds the value of 7.

map

50.map的相关用法

What’s the output`?

1
2
3
4
5
6
[1, 2, 3].map(num => {
if (typeof num === "number") return;
return num * 2;
});

// [undefined, undefined, undefined]

When mapping over the array, the value of num is equal to the element it’s currently looping over. In this case, the elements are numbers, so the condition of the if statement typeof num === "number" returns true. The map function creates a new array and inserts the values returned from the function.

However, we don’t return a value. When we don’t return a value from the function, the function returns undefined. For every element in the array, the function block gets called, so for each element we return undefined.

try-catch throw

52.try catch 和 throw 的用法

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function greeting() {
throw "Hello world!";
}

function sayHi() {
try {
const data = greeting();
console.log("It worked!", data);
} catch (e) {
console.log("Oh no an error:", e);
}
}

sayHi();

// Oh no an error! Hello world!

With the throw statement, we can create custom errors. With this statement, you can throw exceptions. An exception can be a string, a number, a boolean or an object. In this case, our exception is the string 'Hello world'.

With the catch statement, we can specify what to do if an exception is thrown in the try block. An exception is thrown: the string 'Hello world'. e is now equal to that string, which we log. This results in 'Oh an error: Hello world'.

new return

53.new 构造函数如果有显式的 return 呢

What’s the output?

1
2
3
4
5
6
7
8
9
function Car() {
this.make = "Lamborghini";
return { make: "Maserati" };
}

const myCar = new Car();
console.log(myCar.make);

// "Maserati"

When you return a property, the value of the property is equal to the returned value, not the value set in the constructor function. We return the string "Maserati", so myCar.make is equal to "Maserati".

delete

55.delete相关

What’s the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Dog {
constructor(name) {
this.name = name;
}
}

Dog.prototype.bark = function() {
console.log(`Woof I am ${this.name}`);
};

const pet = new Dog("Mara");

pet.bark();

delete Dog.prototype.bark;

pet.bark();

// "Woof I am Mara", TypeError

We can delete properties from objects using the delete keyword, also on the prototype. By deleting a property on the prototype, it is not available anymore in the prototype chain. In this case, the bark function is not available anymore on the prototype after delete Dog.prototype.bark, yet we still try to access it.

When we try to invoke something that is not a function, a TypeError is thrown. In this case TypeError: pet.bark is not a function, since pet.bark is undefined.

import export

57.修改导入的模块

What’s the output?

1
2
3
// counter.js
let counter = 10;
export default counter;
1
2
3
4
5
6
7
8
// index.js
import myCounter from "./counter";

myCounter += 1;

console.log(myCounter);

// Error

An imported module is read-only: you cannot modify the imported module. Only the module that exports them can change its value.

When we try to increment the value of myCounter, it throws an error: myCounter is read-only and cannot be modified.

delete的返回值

58.delete成功删除或者删除失败的返回值

What’s the output?

1
2
3
4
5
6
7
const name = "Lydia";
age = 21;

console.log(delete name);
console.log(delete age);

// false, true

The delete operator returns a boolean value: true on a successful deletion, else it’ll return false. However, variables declared with the var, const or let keyword cannot be deleted using the delete operator.

The name variable was declared with a const keyword, so its deletion is not successful: false is returned. When we set age equal to 21, we actually added a property called age to the global object. You can successfully delete properties from objects this way, also the global object, so delete age returns true.

… operator

60.运算符…的性质

What’s the output?

1
2
3
4
5
6
const user = { name: "Lydia", age: 21 };
const admin = { admin: true, ...user };

console.log(admin);

// { admin: true, name: "Lydia", age: 21 }

It’s possible to combine objects using the spread operator .... It lets you create copies of the key/value pairs of one object, and add them to another object. In this case, we create copies of the user object, and add them to the admin object. The admin object now contains the copied key/value pairs, which results in { admin: true, name: "Lydia", age: 21 }.

Object.defineProperty

61.关于Object.defineProperty

What’s the output?

1
2
3
4
5
6
7
8
const person = { name: "Lydia" };

Object.defineProperty(person, "age", { value: 21 });

console.log(person);
console.log(Object.keys(person));

// { name: "Lydia", age: 21 }, ["name"]

With the defineProperty method, we can add new properties to an object, or modify existing ones. When we add a property to an object using the defineProperty method, they are by default not enumerable. The Object.keys method returns all enumerable property names from an object, in this case only "name".

Properties added using the defineProperty method are immutable by default. You can override this behavior using the writable, configurable and enumerable properties. This way, the defineProperty method gives you a lot more control over the properties you’re adding to an object.

JSON.stringify

62.关于 json.stringify

What’s the output?

1
2
3
4
5
6
7
8
9
10
const settings = {
username: "lydiahallie",
level: 19,
health: 90
};

const data = JSON.stringify(settings, ["level", "health"]);
console.log(data);

// "{"level":19, "health":90}"

The second argument of JSON.stringify is the replacer. The replacer can either be a function or an array, and lets you control what and how the values should be stringified.

If the replacer is an array, only the property names included in the array will be added to the JSON string. In this case, only the properties with the names "level" and "health" are included, "username" is excluded. data is now equal to "{"level":19, "health":90}".

If the replacer is a function, this function gets called on every property in the object you’re stringifying. The value returned from this function will be the value of the property when it’s added to the JSON string. If the value is undefined, this property is excluded from the JSON string.

reduce

65.reduce 的相关性质

What’s the output?

1
2
3
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));

// 1 2 and undefined 3 and undefined 4

reducer 函数接收4个参数:

  1. Accumulator (acc) (累计器)
  2. Current Value (cur) (当前值)
  3. Current Index (idx) (当前索引)
  4. Source Array (src) (源数组)

reducer 函数的返回值将会分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。

reducer 函数还有一个可选参数initialValue, 该参数将作为第一次调用回调函数时的第一个参数的值。如果没有提供initialValue,则将使用数组中的第一个元素。

在上述例子,reduce方法接收的第一个参数(Accumulator)是x, 第二个参数(Current Value)是y。

在第一次调用时,累加器x为1,当前值“y”为2,打印出累加器和当前值:1和2。

例子中我们的回调函数没有返回任何值,只是打印累加器的值和当前值。如果函数没有返回值,则默认返回undefined。 在下一次调用时,累加器为undefined,当前值为“3”, 因此undefined和3被打印出。

在第四次调用时,回调函数依然没有返回值。 累加器再次为 undefined ,当前值为“4”。 undefined和4被打印出。

import require

67.模块引入机制

What’s the output?

1
2
3
4
5
6
7
8
9
10
// index.js
console.log('running index.js');
import { sum } from './sum.js';
console.log(sum(1, 2));

// sum.js
console.log('running sum.js');
export const sum = (a, b) => a + b;

// running sum.js, running index.js, 3

With the import keyword, all imported modules are pre-parsed. This means that the imported modules get run first, the code in the file which imports the module gets executed after.

This is a difference between require() in CommonJS and import! With require(), you can load dependencies on demand while the code is being run. If we would have used require instead of import, running index.js, running sum.js, 3 would have been logged to the console.

generator yield

71.generator和yield相关性质

How can we log the values that are commented out after the console.log statement?

1
2
3
4
5
6
7
8
9
10
11
12
13
function* startGame() {
const answer = yield "Do you love JavaScript?";
if (answer !== "Yes") {
return "Oh wow... Guess we're gone here";
}
return "JavaScript loves you back ❤️";
}

const game = startGame();
console.log(/* 1 */); // Do you love JavaScript?
console.log(/* 2 */); // JavaScript loves you back ❤️

// game.next().value and game.next("Yes").value

A generator function “pauses” its execution when it sees the yield keyword. First, we have to let the function yield the string “Do you love JavaScript?”, which can be done by calling game.next().value.

Every line is executed, until it finds the first yield keyword. There is a yield keyword on the first line within the function: the execution stops with the first yield! This means that the variable answer is not defined yet!

When we call game.next("Yes").value, the previous yield is replaced with the value of the parameters passed to the next() function, "Yes" in this case. The value of the variable answer is now equal to "Yes". The condition of the if-statement returns false, and JavaScript loves you back ❤️ gets logged.

String.raw

72.String.raw 会忽略转义字符

What’s the output?

1
2
3
console.log(String.raw`Hello\nworld`);

// Hello\nworld

String.raw函数是用来获取一个模板字符串的原始字符串的,它返回一个字符串,其中忽略了转义符(\n,\v,\t等)。但反斜杠可能造成问题,因为你可能会遇到下面这种类似情况:

1
2
const path = `C:\Documents\Projects\table.html`
String.raw`${path}`

这将导致:

1
"C:DocumentsProjects able.html"

直接使用String.raw

1
String.raw`C:\Documents\Projects\table.html`

它会忽略转义字符并打印:C:\Documents\Projects\table.html

上述情况,字符串是Hello\nworld被打印出。

async await

73.async的返回值

What’s the output?

1
2
3
4
5
6
7
8
async function getData() {
return await Promise.resolve("I made it!");
}

const data = getData();
console.log(data);

// Promise {<pending>}

An async function always returns a promise. The await still has to wait for the promise to resolve: a pending promise gets returned when we call getData() in order to set data equal to it.

If we wanted to get access to the resolved value "I made it", we could have used the .then() method on data:

1
data.then(res => console.log(res))

This would’ve logged "I made it!"

拓展:注意这里的打印顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
async function getData() {
console.log(await Promise.resolve("I made it!1"))
const a = await Promise.resolve("I made it!2");
console.log(a);
return await Promise.resolve("I made it!3");
}

const data = getData();
console.log(data);

// Promise { <pending> }
// I made it!1
// I made it!2

记忆函数

78.闭包和缓存

What is the output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const add = () => {
const cache = {};
return num => {
if (num in cache) {
return `From cache! ${cache[num]}`;
} else {
const result = num + 10;
cache[num] = result;
return `Calculated! ${result}`;
}
};
};

const addFunction = add();
console.log(addFunction(10));
console.log(addFunction(10));
console.log(addFunction(5 * 2));

// Calculated! 20 From cache! 20 From cache! 20

The add function is a memoized function. With memoization, we can cache the results of a function in order to speed up its execution. In this case, we create a cache object that stores the previously returned values.

If we call the addFunction function again with the same argument, it first checks whether it has already gotten that value in its cache. If that’s the case, the caches value will be returned, which saves on execution time. Else, if it’s not cached, it will calculate the value and store it afterwards.

We call the addFunction function three times with the same value: on the first invocation, the value of the function when num is equal to 10 isn’t cached yet. The condition of the if-statement num in cache returns false, and the else block gets executed: Calculated! 20 gets logged, and the value of the result gets added to the cache object. cache now looks like { 10: 20 }.

The second time, the cache object contains the value that gets returned for 10. The condition of the if-statement num in cache returns true, and 'From cache! 20' gets logged.

The third time, we pass 5 * 2 to the function which gets evaluated to 10. The cache object contains the value that gets returned for 10. The condition of the if-statement num in cache returns true, and 'From cache! 20' gets logged.

import * export

89.import * 和 export 多个

What’s the output?

1
2
3
4
5
6
7
8
9
// module.js 
export default () => "Hello world"
export const name = "Lydia"

// index.js
import * as data from "./module"

console.log(data)
// { default: function default(), name: "Lydia" }

With the import * as name syntax, we import all exports from the module.js file into the index.js file as a new object called data is created. In the module.js file, there are two exports: the default export, and a named export. The default export is a function which returns the string "Hello World", and the named export is a variable called name which has the value of the string "Lydia".

The data object has a default property for the default export, other properties have the names of the named exports and their corresponding values.

prototype

92.常规函数和箭头函数的prototype

What’s the output?

1
2
3
4
5
6
7
8
9
10
function giveLydiaPizza() {
return "Here is pizza!"
}

const giveLydiaChocolate = () => "Here's chocolate... now go hit the gym already."

console.log(giveLydiaPizza.prototype)
console.log(giveLydiaChocolate.prototype)

// { constructor: ...} undefined

Regular functions, such as the giveLydiaPizza function, have a prototype property, which is an object (prototype object) with a constructor property. Arrow functions however, such as the giveLydiaChocolate function, do not have this prototype property. undefined gets returned when trying to access the prototype property using giveLydiaChocolate.prototype.

Object.entries

93.Object.entries的性质

What’s the output?

1
2
3
4
5
6
7
8
9
const person = {
name: "Lydia",
age: 21
}

for (const [x, y] of Object.entries(person)) {
console.log(x, y)
}
// name Lydia and age 21

Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,上述情况返回一个二维数组,数组每个元素是一个包含键和值的数组:

1
[['name','Lydia'],['age',21]]

使用for-of循环,我们可以迭代数组中的每个元素,上述情况是子数组。 我们可以使用const [x,y]在for-of循环中解构子数组。 x等于子数组中的第一个元素,y等于子数组中的第二个元素。

第一个子阵列是[“name”,“Lydia”],其中x等于name,而y等于Lydia。 第二个子阵列是[“age”,21],其中x等于age,而y等于21。

扩展运算符 …

94.剩余参数应该放到最后

What’s the output?

1
2
3
4
5
6
7
function getItems(fruitList, ...args, favoriteFruit) {
return [...fruitList, ...args, favoriteFruit]
}

getItems(["banana", "apple"], "pear", "orange")

// Uncaught SyntaxError: Rest parameter must be last formal parameter

... args是剩余参数,剩余参数的值是一个包含所有剩余参数的数组,并且只能作为最后一个参数。上述示例中,剩余参数是第二个参数,这是不可能的,并会抛出语法错误。

1
2
3
4
5
function getItems(fruitList, favoriteFruit, ...args) {
return [...fruitList, ...args, favoriteFruit]
}

getItems(["banana", "apple"], "pear", "orange")

上述例子是有效的,将会返回数组:[ 'banana', 'apple', 'orange', 'pear' ]

Promise.resolve async await

102:Promise.resolve方式和 async await 方式的不同

What’s the value of output?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const myPromise = () => Promise.resolve('I have resolved!')

function firstFunction() {
myPromise().then(res => console.log(res))
console.log('second')
}

async function secondFunction() {
console.log(await myPromise())
console.log('second')
}

firstFunction()
secondFunction()

// second, I have resolved! and I have resolved!, second

With a promise, we basically say I want to execute this function, but I’ll put it aside for now while it’s running since this might take a while. Only when a certain value is resolved (or rejected), and when the call stack is empty, I want to use this value.

We can get this value with both .then and the await keyword in an async function. Although we can get a promise’s value with both .then and await, they work a bit differently.

In the firstFunction, we (sort of) put the myPromise function aside while it was running, but continued running the other code, which is console.log('second') in this case. Then, the function resolved with the string I have resolved, which then got logged after it saw that the callstack was empty.

With the await keyword in secondFunction, we literally pause the execution of an async function until the value has been resolved before moving to the next line.

This means that it waited for the myPromise to resolve with the value I have resolved, and only once that happened, we moved to the next line: second got logged.

The + operator

103:加号运算符的机制

What’s the value of output?

1
2
3
4
5
6
7
8
9
10
11
const set = new Set()

set.add(1)
set.add("Lydia")
set.add({ name: "Lydia" })

for (let item of set) {
console.log(item + 2)
}

// 3, Lydia2, [Object object]2

The + operator is not only used for adding numerical values, but we can also use it to concatenate strings. Whenever the JavaScript engine sees that one or more values are not a number, it coerces the number into a string.

The first one is 1, which is a numerical value. 1 + 2 returns the number 3.

However, the second one is a string "Lydia". "Lydia" is a string and 2 is a number: 2 gets coerced into a string. "Lydia" and "2" get concatenated, which results in the string "Lydia2".

{ name: "Lydia" } is an object. Neither a number nor an object is a string, so it stringifies both. Whenever we stringify a regular object, it becomes "[Object object]". "[Object object]" concatenated with "2" becomes "[Object object]2".

access properties

106:访问属性 [] 和 . 的不同

What’s its value?

1
2
3
4
5
6
7
8
9
10
11
12
13
const colorConfig = {
red: true,
blue: false,
green: true,
black: true,
yellow: false,
}

const colors = ["pink", "red", "blue"]

console.log(colorConfig.colors[1])

// TypeError

In JavaScript, we have two ways to access properties on an object: bracket notation, or dot notation. In this example, we use dot notation (colorConfig.colors) instead of bracket notation (colorConfig["colors"]).

With dot notation, JavaScript tries to find the property on the object with that exact name. In this example, JavaScript tries to find a property called colors on the colorConfig object. There is no proprety called colors, so this returns undefined. Then, we try to access the value of the first element by using [1]. We cannot do this on a value that’s undefined, so it throws a TypeError: Cannot read property '1' of undefined.

JavaScript interprets (or unboxes) statements. When we use bracket notation, it sees the first opening bracket [ and keeps going until it finds the closing bracket ]. Only then, it will evaluate the statement. If we would’ve used colorConfig[colors[1]], it would have returned the value of the red property on the colorConfig object.

modifies the original array

108:数组的这些方法哪个不改变原数组?

Which of these methods modifies the original array?

1
2
3
4
5
6
7
8
9
10
const emojis = ['✨', '🥑', '😍']

emojis.map(x => x + '✨')
emojis.filter(x => x !== '🥑')
emojis.find(x => x !== '🥑')
emojis.reduce((acc, cur) => acc + '✨')
emojis.slice(1, 2, '✨')
emojis.splice(1, 2, '✨')

// splice

With splice method, we modify the original array by deleting, replacing or adding elements. In this case, we removed 2 items from index 1 (we removed '🥑' and '😍') and added the ✨ emoji instead.

map, filter and slice return a new array, find returns an element, and reduce returns a reduced value.

reference

109:复制数组的一个值然后改变会怎样呢?

What’s the output?

1
2
3
4
5
6
7
8
const food = ['🍕', '🍫', '🥑', '🍔']
const info = { favoriteFood: food[0] }

info.favoriteFood = '🍝'

console.log(food)

// ['🍕', '🍫', '🥑', '🍔']

We set the value of the favoriteFood property on the info object equal to the string with the pizza emoji, '🍕'. A string is a primitive data type. In JavaScript, primitive data types act by reference

In JavaScript, primitive data types (everything that’s not an object) interact by value. In this case, we set the value of the favoriteFood property on the info object equal to the value of the first element in the food array, the string with the pizza emoji in this case ('🍕'). A string is a primitive data type, and interact by value (see my blogpost if you’re interested in learning more)

Then, we change the value of the favoriteFood property on the info object. The food array hasn’t changed, since the value of favoriteFood was merely a copy of the value of the first element in the array, and doesn’t have a reference to the same spot in memory as the element on food[0]. When we log food, it’s still the original array, ['🍕', '🍫', '🥑', '🍔'].

1…222324…32

gzl

96 日志
14 分类
37 标签
© 2020 gzl
由 Hexo 强力驱动 v3.7.1
|
主题 – NexT.Pisces v7.2.0