原文地址: https://github.com/lydiahallie/javascript-questions/blob/master/zh-CN/README-zh_CN.md (github有时比较慢,留个备份)
function sayHi() {
console.log(name)
console.log(age)
var name = 'Lydia'
let age = 21
}
sayHi()
Lydia
和 undefined
Lydia
和 ReferenceError
ReferenceError
和 21
undefined
和 ReferenceError
在函数内部,我们首先通过 var
关键字声明了 name
变量。这意味着变量被提升了(内存空间在创建阶段就被设置好了),直到程序运行到定义变量位置之前默认值都是 undefined
。因为当我们打印 name
变量时还没有执行到定义变量的位置,因此变量的值保持为 undefined
。
通过 let
和 const
关键字声明的变量也会提升,但是和 var
不同,它们不会被初始化。在我们声明(初始化)之前是不能访问它们的。这个行为被称之为暂时性死区。当我们试图在声明之前访问它们时,JavaScript 将会抛出一个 ReferenceError
错误。
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1)
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1)
}
0 1 2
和 0 1 2
0 1 2
和 3 3 3
3 3 3
和 0 1 2
由于 JavaScript 的事件循环,setTimeout
回调会在遍历结束后才执行。因为在第一个遍历中遍历 i
是通过 var
关键字声明的,所以这个值是全局作用域下的。在遍历过程中,我们通过一元操作符 ++
来每次递增 i
的值。当 setTimeout
回调执行的时候,i
的值等于 3。
在第二个遍历中,遍历 i
是通过 let
关键字声明的:通过 let
和 const
关键字声明的变量是拥有块级作用域(指的是任何在 {} 中的内容)。在每次的遍历过程中,i
都有一个新值,并且每个值都在循环内的作用域中。
const shape = {
radius: 10,
diameter() {
return this.radius * 2
},
perimeter: () => 2 * Math.PI * this.radius
}
shape.diameter()
shape.perimeter()
20
and 62.83185307179586
20
and NaN
20
and 63
NaN
and 63
注意 diameter
的值是一个常规函数,但是 perimeter
的值是一个箭头函数。
对于箭头函数,this
关键字指向的是它当前周围作用域(简单来说是包含箭头函数的常规函数,如果没有常规函数的话就是全局对象),这个行为和常规函数不同。这意味着当我们调用 perimeter
时,this
不是指向 shape
对象,而是它的周围作用域(在例子中是 window
)。
在 window
中没有 radius
这个属性,因此返回 undefined
。
+true;
!"Lydia";
1
and false
false
and NaN
false
and false
一元操作符加号尝试将 bool 转为 number。true
转换为 number 的话为 1
,false
为 0
。
字符串 'Lydia'
是一个真值,真值取反那么就返回 false
。
const bird = {
size: 'small'
}
const mouse = {
name: 'Mickey',
small: true
}
mouse.bird.size
是无效的mouse[bird.size]
是无效的mouse[bird["size"]]
是无效的
在 JavaScript 中,所有对象的 keys 都是字符串(除非对象是 Symbol)。尽管我们可能不会定义它们为字符串,但它们在底层总会被转换为字符串。
当我们使用括号语法时([]),JavaScript 会解释(或者 unboxes)语句。它首先看到第一个开始括号 [
并继续前进直到找到结束括号 ]
。只有这样,它才会计算语句的值。
mouse[bird.size]
:首先计算 bird.size
,这会得到 small
。mouse["small"]
返回 true
。
然后使用点语法的话,上面这一切都不会发生。mouse
没有 bird
这个 key,这也就意味着 mouse.bird
是 undefined
。然后当我们使用点语法 mouse.bird.size
时,因为 mouse.bird
是 undefined
,这也就变成了 undefined.size
。这个行为是无效的,并且会抛出一个错误类似 Cannot read property "size" of undefined
。
let c = { greeting: 'Hey!' }
let d
d = c
c.greeting = 'Hello'
console.log(d.greeting)
Hello
undefined
ReferenceError
TypeError
在 JavaScript 中,当设置两个对象彼此相等时,它们会通过引用进行交互。
首先,变量 c
的值是一个对象。接下来,我们给 d
分配了一个和 c
对象相同的引用。
因此当我们改变其中一个对象时,其实是改变了所有的对象。
let a = 3
let b = new Number(3)
let c = 3
console.log(a == b)
console.log(a === b)
console.log(b === c)
true
false
true
false
false
true
true
false
false
false
true
true
new Number()
是一个内建的函数构造器。虽然它看着像是一个 number,但它实际上并不是一个真实的 number:它有一堆额外的功能并且它是一个对象。
当我们使用 ==
操作符时,它只会检查两者是否拥有相同的值。因为它们的值都是 3
,因此返回 true
。
然后,当我们使用 ===
操作符时,两者的值以及类型都应该是相同的。new Number()
是一个对象而不是 number,因此返回 false
。
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor
return this.newColor
}
constructor({ newColor = 'green' } = {}) {
this.newColor = newColor
}
}
const freddie = new Chameleon({ newColor: 'purple' })
freddie.colorChange('orange')
orange
purple
green
TypeError
colorChange
是一个静态方法。静态方法被设计为只能被创建它们的构造器使用(也就是 Chameleon
),并且不能传递给实例。因为 freddie
是一个实例,静态方法不能被实例使用,因此抛出了 TypeError
错误。
let greeting
greetign = {} // Typo!
console.log(greetign)
{}
ReferenceError: greetign is not defined
undefined
代码打印出了一个对象,这是因为我们在全局对象上创建了一个空对象!当我们将 greeting
写错成 greetign
时,JS 解释器实际在上浏览器中将它视为 global.greetign = {}
(或者 window.greetign = {}
)。
为了避免这个为题,我们可以使用 `“use strict”。这能确保当你声明变量时必须赋值。
function bark() {
console.log('Woof!')
}
bark.animal = 'dog'
SyntaxError
. 你不能通过这种方式给函数增加属性。undefined
ReferenceError
这在 JavaScript 中是可以的,因为函数是对象!(除了基本类型之外其他都是对象)
函数是一个特殊的对象。你写的这个代码其实不是一个实际的函数。函数是一个拥有属性的对象,并且属性也可被调用。
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const member = new Person("Lydia", "Hallie");
Person.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
}
console.log(member.getFullName());
TypeError
SyntaxError
Lydia Hallie
undefined
undefined
你不能像常规对象那样,给构造函数添加属性。如果你想一次性给所有实例添加特性,你应该使用原型。因此本例中,使用如下方式:
Person.prototype.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
}
这才会使 member.getFullName()
起作用。为什么这么做有益的?假设我们将这个方法添加到构造函数本身里。也许不是每个 Person
实例都需要这个方法。这将浪费大量内存空间,因为它们仍然具有该属性,这将占用每个实例的内存空间。相反,如果我们只将它添加到原型中,那么它只存在于内存中的一个位置,但是所有实例都可以访问它!
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
Person {firstName: "Lydia", lastName: "Hallie"}
and Person {firstName: "Sarah", lastName: "Smith"}
Person {firstName: "Lydia", lastName: "Hallie"}
and {}
Person {firstName: "Lydia", lastName: "Hallie"}
and ReferenceError
对于 sarah
,我们没有使用 new
关键字。当使用 new
时,this
引用我们创建的空对象。当未使用 new
时,this
引用的是全局对象(global object)。
我们说 this.firstName
等于 "Sarah"
,并且 this.lastName
等于 "Smith"
。实际上我们做的是,定义了 global.firstName = 'Sarah'
和 global.lastName = 'Smith'
。而 sarah
本身是 undefined
。
在捕获(capturing)阶段中,事件从祖先元素向下传播到目标元素。当事件达到目标(target)元素后,冒泡(bubbling)才开始。
除了基本对象(base object),所有对象都有原型。基本对象可以访问一些方法和属性,比如 .toString
。这就是为什么你可以使用内置的 JavaScript 方法!所有这些方法在原型上都是可用的。虽然 JavaScript 不能直接在对象上找到这些方法,但 JavaScript 会沿着原型链找到它们,以便于你使用。
function sum(a, b) {
return a + b
}
sum(1, '2')
NaN
TypeError
"12"
3
JavaScript 是一种动态类型语言:我们不指定某些变量的类型。值可以在你不知道的情况下自动转换成另一种类型,这种类型称为隐式类型转换(implicit type coercion)。Coercion 是指将一种类型转换为另一种类型。
在本例中,JavaScript 将数字 1
转换为字符串,以便函数有意义并返回一个值。在数字类型(1
)和字符串类型('2'
)相加时,该数字被视为字符串。我们可以连接字符串,比如 "Hello" + "World"
,这里发生的是 "1" + "2"
,它返回 "12"
。
let number = 0
console.log(number++)
console.log(++number)
console.log(number)
1
1
2
1
2
2
0
2
2
0
1
2
一元后自增运算符 ++
:
0
)1
)一元前自增运算符 ++
:
2
)2
)结果是 0 2 2
.
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`
"Lydia"
21
["", " is ", " years old"]
["", " is ", " years old"]
"Lydia"
21
"Lydia"
["", " is ", " years old"]
21
如果使用标记模板字面量,第一个参数的值总是包含字符串的数组。其余的参数获取的是传递的表达式的值!
function checkAge(data) {
if (data === { age: 18 }) {
console.log('You are an adult!')
} else if (data == { age: 18 }) {
console.log('You are still an adult.')
} else {
console.log(`Hmm.. You don't have an age I guess`)
}
}
checkAge({ age: 18 })
You are an adult!
You are still an adult.
Hmm.. You don't have an age I guess
在测试相等性时,基本类型通过它们的值(value)进行比较,而对象通过它们的引用(reference)进行比较。JavaScript 检查对象是否具有对内存中相同位置的引用。
题目中我们正在比较的两个对象不是同一个引用:作为参数传递的对象引用的内存位置,与用于判断相等的对象所引用的内存位置并不同。
这也是 { age: 18 } === { age: 18 }
和 { age: 18 } == { age: 18 }
都返回 false
的原因。
function getAge(...args) {
console.log(typeof args)
}
getAge(21)
"number"
"array"
"object"
"NaN"
扩展运算符(...args
)会返回实参组成的数组。而数组是对象,因此 typeof args
返回 "object"
。
function getAge() {
'use strict'
age = 21
console.log(age)
}
getAge()
21
undefined
ReferenceError
TypeError
使用 "use strict"
,你可以确保不会意外地声明全局变量。我们从来没有声明变量 age
,因为我们使用 "use strict"
,它将抛出一个引用错误。如果我们不使用 "use strict"
,它就会工作,因为属性 age
会被添加到全局对象中了。
const sum = eval('10*10+5')
105
"105"
TypeError
"10*10+5"
代码以字符串形式传递进来,eval
对其求值。如果它是一个表达式,就像本例中那样,它对表达式求值。表达式是 10 * 10 + 5
。这将返回数字 105
。
sessionStorage.setItem('cool_secret', 123)
关闭 tab 标签页 后,sessionStorage
存储的数据才会删除。
如果使用 localStorage
,那么数据将永远在那里,除非调用了 localStorage.clear()
。
var num = 8
var num = 10
console.log(num)
8
10
SyntaxError
ReferenceError
使用 var
关键字,你可以用相同的名称声明多个变量。然后变量将保存最新的值。
你不能使用 let
或 const
来实现这一点,因为它们是块作用域的。
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)
false
true
false
true
false
true
true
true
true
true
false
true
true
true
true
true
所有对象的键(不包括 Symbol)在底层都是字符串,即使你自己没有将其作为字符串输入。这就是为什么 obj.hasOwnProperty('1')
也返回 true
。
对于集合,它不是这样工作的。在我们的集合中没有 '1'
:set.has('1')
返回 false
。它有数字类型为 1
,set.has(1)
返回 true
。
const obj = { a: 'one', b: 'two', a: 'three' }
console.log(obj)
{ a: "one", b: "two" }
{ b: "two", a: "three" }
{ a: "three", b: "two" }
SyntaxError
如果你有两个名称相同的键,则键会被替换掉。它仍然位于第一个键出现的位置,但是值是最后出现那个键的值。
基本执行上下文是全局执行上下文:它是代码中随处可访问的内容。
for (let i = 1; i < 5; i++) {
if (i === 3) continue
console.log(i)
}
1
2
1
2
3
1
2
4
1
3
4
如果某个条件返回 true
,则 continue
语句跳过本次迭代。
String.prototype.giveLydiaPizza = () => {
return 'Just give Lydia pizza already!'
}
const name = 'Lydia'
name.giveLydiaPizza()
"Just give Lydia pizza already!"
TypeError: not a function
SyntaxError
undefined
String
是内置的构造函数,我们可以向它添加属性。我只是在它的原型中添加了一个方法。基本类型字符串被自动转换为字符串对象,由字符串原型函数生成。因此,所有 string(string 对象)都可以访问该方法!
const a = {}
const b = { key: 'b' }
const c = { key: 'c' }
a[b] = 123
a[c] = 456
console.log(a[b])
123
456
undefined
ReferenceError
对象的键被自动转换为字符串。我们试图将一个对象 b
设置为对象 a
的键,且相应的值为 123
。
然而,当字符串化一个对象时,它会变成 "[object Object]"
。因此这里说的是,a["[object Object]"] = 123
。然后,我们再一次做了同样的事情,c
是另外一个对象,这里也有隐式字符串化,于是,a["[object Object]"] = 456
。
然后,我们打印 a[b]
,也就是 a["[object Object]"]
。之前刚设置为 456
,因此返回的是 456
。
const foo = () => console.log('First')
const bar = () => setTimeout(() => console.log('Second'))
const baz = () => console.log('Third')
bar()
foo()
baz()
First
Second
Third
First
Third
Second
Second
First
Third
Second
Third
First
我们有一个 setTimeout
函数,并首先调用它。然而,它是最后打印日志的。
这是因为在浏览器中,我们不仅有运行时引擎,还有一个叫做 WebAPI
的东西。WebAPI
提供了 setTimeout
函数,也包含其他的,例如 DOM。
将 callback 推送到 WebAPI 后,setTimeout
函数本身(但不是回调!)将从栈中弹出。
现在,foo
被调用,打印 "First"
。
foo
从栈中弹出,baz
被调用. 打印 "Third"
。
WebAPI 不能随时向栈内添加内容。相反,它将回调函数推到名为 queue 的地方。
这就是事件循环开始工作的地方。一个事件循环查看栈和任务队列。如果栈是空的,它接受队列上的第一个元素并将其推入栈。
bar
被调用,打印 "Second"
,然后它被栈弹出。
<div onclick="console.log('first div')">
<div onclick="console.log('second div')">
<button onclick="console.log('button')">
Click!
button>
div>
div>
div
div
button
导致事件的最深嵌套的元素是事件的 target。你可以通过 event.stopPropagation
来停止冒泡。
<div onclick="console.log('div')">
<p onclick="console.log('p')">
Click here!
p>
div>
p
div
div
p
p
div
如果我们点击 p
,我们会看到两个日志:p
和 div
。在事件传播期间,有三个阶段:捕获、目标和冒泡。默认情况下,事件处理程序在冒泡阶段执行(除非将 useCapture
设置为 true
)。它从嵌套最深的元素向外传播。
const person = { name: 'Lydia' }
function sayHi(age) {
console.log(`${this.name} is ${age}`)
}
sayHi.call(person, 21)
sayHi.bind(person, 21)
undefined is 21
Lydia is 21
function
function
Lydia is 21
Lydia is 21
Lydia is 21
function
使用这两种方法,我们都可以传递我们希望 this
关键字引用的对象。但是,.call
是立即执行的。
.bind
返回函数的副本,但带有绑定上下文!它不是立即执行的。
function sayHi() {
return (() => 0)()
}
typeof sayHi()
"object"
"number"
"function"
"undefined"
sayHi
方法返回的是立即执行函数(IIFE)的返回值.此立即执行函数的返回值是 0
, 类型是 number
参考:只有7种内置类型:null
,undefined
,boolean
,number
,string
,object
, symbol
和 bigint
。 function
不是一种类型,函数是对象,它的类型是object
。
0
new Number(0)
('')
(' ')
new Boolean(false)
undefined
0
, ''
, undefined
0
, new Number(0)
, ''
, new Boolean(false)
, undefined
0
, ''
, new Boolean(false)
, undefined
只有 6 种 falsy 值:
undefined
null
NaN
0
''
(empty string)false
Function
构造函数, 比如 new Number
和 new Boolean
,是 truthy。
console.log(typeof typeof 1)
"number"
"string"
"object"
"undefined"
typeof 1
返回 "number"
。
typeof "number"
返回 "string"
。
const numbers = [1, 2, 3]
numbers[10] = 11
console.log(numbers)
[1, 2, 3, 7 x null, 11]
[1, 2, 3, 11]
[1, 2, 3, 7 x empty, 11]
SyntaxError
当你为数组设置超过数组长度的值的时候, JavaScript 会创建名为 “empty slots” 的东西。它们的值实际上是 undefined
。你会看到以下场景:
[1, 2, 3, 7 x empty, 11]
这取决于你的运行环境(每个浏览器,以及 node 环境,都有可能不同)
(() => {
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
undefined
undefined
undefined
1
1
2
1
undefined
undefined
catch
代码块接收参数 x
。当我们传递参数时,这与之前定义的变量 x
不同 。这个 x
是属于 catch
块级作用域的。
然后,我们将块级作用域中的变量赋值为 1
,同时也设置了变量 y
的值。现在,我们打印块级作用域中的变量 x
,值为 1
。
catch
块之外的变量 x
的值仍为 undefined
, y
的值为 2
。当我们在 catch
块之外执行 console.log(x)
时,返回 undefined
,y
返回 2
。
JavaScript 只有基本类型和对象。
基本类型包括 boolean
, null
, undefined
, bigint
, number
, string
, symbol
。
[[0, 1], [2, 3]].reduce(
(acc, cur) => {
return acc.concat(cur)
},
[1, 2]
)
[0, 1, 2, 3, 1, 2]
[6, 1, 2]
[1, 2, 0, 1, 2, 3]
[1, 2, 6]
[1, 2]
是初始值。初始值将会作为首次调用时第一个参数 acc
的值。在第一次执行时, acc
的值是 [1, 2]
, cur
的值是 [0, 1]
。合并它们,结果为 [1, 2, 0, 1]
。
第二次执行, acc
的值是 [1, 2, 0, 1]
, cur
的值是 [2, 3]
。合并它们,最终结果为 [1, 2, 0, 1, 2, 3]
!!null
!!''
!!1
false
true
false
false
false
true
false
true
true
true
true
false
null
是 falsy。 !null
的值是 true
。 !true
的值是 false
。
""
是 falsy。 !""
的值是 true
。 !true
的值是 false
。
1
是 truthy。 !1
的值是 false
。 !false
的值是 true
。
setInterval
方法的返回值是什么?setInterval(() => console.log('Hi'), 1000)
undefined
setInterval
返回一个唯一的 id。此 id 可被用于 clearInterval
函数来取消定时。
[...'Lydia']
["L", "y", "d", "i", "a"]
["Lydia"]
[[], "Lydia"]
[["L", "y", "d", "i", "a"]]
string 类型是可迭代的。扩展运算符将迭代的每个字符映射成一个元素。
function* generator(i) {
yield i;
yield i * 2;
}
const gen = generator(10);
console.log(gen.next().value);
console.log(gen.next().value);
[0, 10], [10, 20]
20, 20
10, 20
0, 10 and 10, 20
一般的函数在执行之后是不能中途停下的。但是,生成器函数却可以中途“停下”,之后可以再从停下的地方继续。当生成器遇到yield
关键字的时候,会生成yield
后面的值。注意,生成器在这种情况下不 返回 (return )值,而是 生成 (yield)值。
首先,我们用10
作为参数i
来初始化生成器函数。然后使用next()
方法一步步执行生成器。第一次执行生成器的时候,i
的值为10
,遇到第一个yield
关键字,它要生成i
的值。此时,生成器“暂停”,生成了10
。
然后,我们再执行next()
方法。生成器会从刚才暂停的地方继续,这个时候i
还是10
。于是我们走到了第二个yield
关键字处,这时候需要生成的值是i*2
,i
为10
,那么此时生成的值便是20
。所以这道题的最终结果是10,20
。
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));
"one"
"two"
"two" "one"
"one" "two"
当我们向Promise.race
方法中传入多个Promise
时,会进行 优先 解析。在这个例子中,我们用setTimeout
给firstPromise
和secondPromise
分别设定了500ms和100ms的定时器。这意味着secondPromise
会首先解析出字符串two
。那么此时res
参数即为two
,是为输出结果。
let person = { name: "Lydia" };
const members = [person];
person = null;
console.log(members);
null
[null]
[{}]
[{ name: "Lydia" }]
首先我们声明了一个拥有name
属性的对象 person
。
然后我们又声明了一个变量members
. 将首个元素赋值为变量person
。 当设置两个对象彼此相等时,它们会通过 引用 进行交互。但是当你将引用从一个变量分配至另一个变量时,其实只是执行了一个 复制 操作。(注意一点,他们的引用 并不相同!)
接下来我们让person
等于null
。
我们没有修改数组第一个元素的值,而只是修改了变量person
的值,因为元素(复制而来)的引用与person
不同。members
的第一个元素仍然保持着对原始对象的引用。当我们输出members
数组时,第一个元素会将引用的对象打印出来。
const person = {
name: "Lydia",
age: 21
};
for (const item in person) {
console.log(item);
}
{ name: "Lydia" }, { age: 21 }
"name", "age"
"Lydia", 21
["name", "Lydia"], ["age", 21]
在for-in
循环中,我们可以通过对象的key来进行迭代,也就是这里的name
和age
。在底层,对象的key都是字符串(如果他们不是Symbol的话)。在每次循环中,我们将item
设定为当前遍历到的key.所以一开始,item
是name
,之后 item
输出的则是age
。
console.log(3 + 4 + "5");
"345"
"75"
12
"12"
当所有运算符的 优先级 相同时,计算表达式需要确定运算符的结合顺序,即从右到左还是从左往右。在这个例子中,我们只有一类运算符+
,对于加法来说,结合顺序就是从左到右。
3 + 4
首先计算,得到数字7
.
由于类型的强制转换,7 + '5'
的结果是"75"
. JavaScript将7
转换成了字符串,可以参考问题15.我们可以用+
号把两个字符串连接起来。 "7" + "5"
就得到了"75"
.
num
的值是什么?const num = parseInt("7*6", 10);
42
"42"
7
NaN
只返回了字符串中第一个字母. 设定了 进制 后 (也就是第二个参数,指定需要解析的数字是什么进制: 十进制、十六机制、八进制、二进制等等……),parseInt
检查字符串中的字符是否合法. 一旦遇到一个在指定进制中不合法的字符后,立即停止解析并且忽略后面所有的字符。
*
就是不合法的数字字符。所以只解析到"7"
,并将其解析为十进制的7
. num
的值即为7
.
[1, 2, 3].map(num => {
if (typeof num === "number") return;
return num * 2;
});
[]
[null, null, null]
[undefined, undefined, undefined]
[ 3 x empty ]
对数组进行映射的时候,num
就是当前循环到的元素. 在这个例子中,所有的映射都是number类型,所以if中的判断typeof num === "number"
结果都是true
.map函数创建了新数组并且将函数的返回值插入数组。
但是,没有任何值返回。当函数没有返回任何值时,即默认返回undefined
.对数组中的每一个元素来说,函数块都得到了这个返回值,所以结果中每一个元素都是undefined
.
function getInfo(member, year) {
member.name = "Lydia";
year = "1998";
}
const person = { name: "Sarah" };
const birthYear = "1997";
getInfo(person, birthYear);
console.log(person, birthYear);
{ name: "Lydia" }, "1997"
{ name: "Sarah" }, "1998"
{ name: "Lydia" }, "1998"
{ name: "Sarah" }, "1997"
普通参数都是 值 传递的,而对象则不同,是 引用 传递。所以说,birthYear
是值传递,因为他是个字符串而不是对象。当我们对参数进行值传递时,会创建一份该值的 复制 。(可以参考问题46)
变量birthYear
有一个对"1997"
的引用,而传入的参数也有一个对"1997"
的引用,但二者的引用并不相同。当我们通过给 year
赋值"1998"
来更新year
的值的时候我们只是更新了year
(的引用)。此时birthYear
仍然是"1997"
.
而person
是个对象。参数member
引用与之 相同的 对象。当我们修改member
所引用对象的属性时,person
的相应属性也被修改了,因为他们引用了相同的对象. person
的 name
属性也变成了 "Lydia"
.
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();
"It worked! Hello world!"
"Oh no an error: undefined
SyntaxError: can only throw Error objects
"Oh no an error: Hello world!
通过throw
语句,我么可以创建自定义错误。 而通过它,我们可以抛出异常。异常可以是一个字符串, 一个 数字, 一个 布尔类型 或者是一个 对象。在本例中,我们的异常是字符串'Hello world'
.
通过 catch
语句,我们可以设定当try
语句块中抛出异常后应该做什么处理。在本例中抛出的异常是字符串'Hello world'
. e
就是这个字符串,因此被输出。最终结果就是'Oh an error: Hello world'
.
function Car() {
this.make = "Lamborghini";
return { make: "Maserati" };
}
const myCar = new Car();
console.log(myCar.make);
"Lamborghini"
"Maserati"
ReferenceError
TypeError
返回属性的时候,属性的值等于 返回的 值,而不是构造函数中设定的值。我们返回了字符串 "Maserati"
,所以 myCar.make
等于"Maserati"
.
(() => {
let x = (y = 10);
})();
console.log(typeof x);
console.log(typeof y);
"undefined", "number"
"number", "number"
"object", "number"
"number", "undefined"
let x = y = 10;
是下面这个表达式的缩写:
y = 10;
let x = y;
我们设定y
等于10
时,我们实际上增加了一个属性y
给全局对象(浏览器里的window
, Nodejs里的global
)。在浏览器中, window.y
等于10
.
然后我们声明了变量x
等于y
,也是10
.但变量是使用 let
声明的,它只作用于 块级作用域, 仅在声明它的块中有效;就是案例中的立即调用表达式(IIFE)。使用typeof
操作符时, 操作值 x
没有被定义:因为我们在x
声明块的外部,无法调用它。这就意味着x
未定义。未分配或是未声明的变量类型为"undefined"
. console.log(typeof x)
返回"undefined"
.
而我们创建了全局变量y
,并且设定y
等于10
.这个值在我们的代码各处都访问的到。 y
已经被定义了,而且有一个"number"
类型的值。 console.log(typeof y)
返回"number"
.
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
"Woof I am Mara"
,"Woof I am Mara"
"Woof I am Mara"
, undefined
TypeError
, TypeError
我们可以用delete
关键字删除对象的属性,对原型也是适用的。删除了原型的属性后,该属性在原型链上就不可用了。在本例中,函数bark
在执行了delete Dog.prototype.bark
后不可用, 然而后面的代码还在调用它。
当我们尝试调用一个不存在的函数时TypeError
异常会被抛出。在本例中就是 TypeError: pet.bark is not a function
,因为pet.bark
是undefined
.
const set = new Set([1, 1, 2, 3, 4]);
console.log(set);
[1, 1, 2, 3, 4]
[1, 2, 3, 4]
{1, 1, 2, 3, 4}
{1, 2, 3, 4}
Set
对象是独一无二的值的集合:也就是说同一个值在其中仅出现一次。
我们传入了数组[1, 1, 2, 3, 4]
,他有一个重复值1
.以为一个集合里不能有两个重复的值,其中一个就被移除了。所以结果是 {1, 2, 3, 4}
.
// counter.js
let counter = 10;
export default counter;
// index.js
import myCounter from "./counter";
myCounter += 1;
console.log(myCounter);
10
11
Error
NaN
引入的模块是 只读 的: 你不能修改引入的模块。只有导出他们的模块才能修改其值。
当我们给myCounter
增加一个值的时候会抛出一个异常: myCounter
是只读的,不能被修改。
const name = "Lydia";
age = 21;
console.log(delete name);
console.log(delete age);
false
, true
"Lydia"
, 21
true
, true
undefined
, undefined
delete
操作符返回一个布尔值: true
指删除成功,否则返回false
. 但是通过 var
, const
或 let
关键字声明的变量无法用 delete
操作符来删除。
name
变量由const
关键字声明,所以删除不成功:返回 false
. 而我们设定age
等于21
时,我们实际上添加了一个名为age
的属性给全局对象。对象中的属性是可以删除的,全局对象也是如此,所以delete age
返回true
.
const numbers = [1, 2, 3, 4, 5];
const [y] = numbers;
console.log(y);
[[1, 2, 3, 4, 5]]
[1, 2, 3, 4, 5]
1
[1]
我们可以通过解构赋值来解析来自对象的数组或属性的值,比如说:
[a, b] = [1, 2];
a
的值现在是1
,b
的值现在是2
.而在题目中,我们是这么做的:
[y] = [1, 2, 3, 4, 5];
也就是说,y
等于数组的第一个值就是数字1
.我们输出y
, 返回1
.
const user = { name: "Lydia", age: 21 };
const admin = { admin: true, ...user };
console.log(admin);
{ admin: true, user: { name: "Lydia", age: 21 } }
{ admin: true, name: "Lydia", age: 21 }
{ admin: true, user: ["Lydia", 21] }
{ admin: true }
扩展运算符...
为对象的组合提供了可能。你可以复制对象中的键值对,然后把它们加到另一个对象里去。在本例中,我们复制了user
对象键值对,然后把它们加入到admin
对象中。admin
对象就拥有了这些键值对,所以结果为{ admin: true, name: "Lydia", age: 21 }
.
const person = { name: "Lydia" };
Object.defineProperty(person, "age", { value: 21 });
console.log(person);
console.log(Object.keys(person));
{ name: "Lydia", age: 21 }
, ["name", "age"]
{ name: "Lydia", age: 21 }
, ["name"]
{ name: "Lydia"}
, ["name", "age"]
{ name: "Lydia"}
, ["age"]
通过defineProperty
方法,我们可以给对象添加一个新属性,或者修改已经存在的属性。而我们使用defineProperty
方法给对象添加了一个属性之后,属性默认为 不可枚举(not enumerable). Object.keys
方法仅返回对象中 可枚举(enumerable) 的属性,因此只剩下了"name"
.
用defineProperty
方法添加的属性默认不可变。你可以通过writable
, configurable
和 enumerable
属性来改变这一行为。这样的话, 相比于自己添加的属性,defineProperty
方法添加的属性有了更多的控制权。
const settings = {
username: "lydiahallie",
level: 19,
health: 90
};
const data = JSON.stringify(settings, ["level", "health"]);
console.log(data);
"{"level":19, "health":90}"
"{"username": "lydiahallie"}"
"["level", "health"]"
"{"username": "lydiahallie", "level":19, "health":90}"
JSON.stringify
的第二个参数是 替代者(replacer). 替代者(replacer)可以是个函数或数组,用以控制哪些值如何被转换为字符串。
如果替代者(replacer)是个 数组 ,那么就只有包含在数组中的属性将会被转化为字符串。在本例中,只有名为"level"
和 "health"
的属性被包括进来, "username"
则被排除在外。 data
就等于 "{"level":19, "health":90}"
.
而如果替代者(replacer)是个 函数,这个函数将被对象的每个属性都调用一遍。
函数返回的值会成为这个属性的值,最终体现在转化后的JSON字符串中(译者注:Chrome下,经过实验,如果所有属性均返回同一个值的时候有异常,会直接将返回值作为结果输出而不会输出JSON字符串),而如果返回值为undefined
,则该属性会被排除在外。
let num = 10;
const increaseNumber = () => num++;
const increasePassedNumber = number => number++;
const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);
console.log(num1);
console.log(num2);
10
, 10
10
, 11
11
, 11
11
, 12
一元操作符 ++
先返回 操作值, 再累加 操作值。num1
的值是10
, 因为increaseNumber
函数首先返回num
的值,也就是10
,随后再进行 num
的累加。
num2
是10
因为我们将 num1
传入increasePassedNumber
. number
等于10
(num1
的值。同样道理,++
先返回 操作值, 再累加 操作值。) number
是10
,所以num2
也是10
.
const value = { number: 10 };
const multiply = (x = { ...value }) => {
console.log(x.number *= 2);
};
multiply();
multiply();
multiply(value);
multiply(value);
20
, 40
, 80
, 160
20
, 40
, 20
, 40
20
, 20
, 20
, 40
NaN
, NaN
, 20
, 40
在ES6中,我们可以使用默认值初始化参数。如果没有给函数传参,或者传的参值为 "undefined"
,那么参数的值将是默认值。上述例子中,我们将 value
对象进行了解构并传到一个新对象中,因此 x
的默认值为 {number:10}
。
默认参数在调用时才会进行计算,每次调用函数时,都会创建一个新的对象。我们前两次调用 multiply
函数且不传递值,那么每一次 x
的默认值都为 {number:10}
,因此打印出该数字的乘积值为20
。
第三次调用 multiply
时,我们传递了一个参数,即对象value
。 *=
运算符实际上是x.number = x.number * 2
的简写,我们修改了x.number
的值,并打印出值20
。
第四次,我们再次传递value
对象。 x.number
之前被修改为20
,所以x.number * = 2
打印为40
。
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
1
2
and 3
3
and 6
4
1
2
and 2
3
and 3
4
1
undefined
and 2
undefined
and 3
undefined
and 4
undefined
1
2
and undefined
3
and undefined
4
reducer
函数接收4个参数:
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
被打印出。
Dog
类?class Dog {
constructor(name) {
this.name = name;
}
};
class Labrador extends Dog {
// 1
constructor(name, size) {
this.size = size;
}
// 2
constructor(name, size) {
super(name);
this.size = size;
}
// 3
constructor(size) {
super(name);
this.size = size;
}
// 4
constructor(name, size) {
this.name = name;
this.size = size;
}
};
在子类中,在调用super
之前不能访问到this
关键字。 如果这样做,它将抛出一个ReferenceError
:1和4将引发一个引用错误。
使用super
关键字,需要用给定的参数来调用父类的构造函数。 父类的构造函数接收name
参数,因此我们需要将name
传递给super
。
Labrador
类接收两个参数,name
参数是由于它继承了Dog
,size
作为Labrador
类的额外属性,它们都需要传递给Labrador
的构造函数,因此使用构造函数2正确完成。
// 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 index.js
, running sum.js
, 3
running sum.js
, running index.js
, 3
running sum.js
, 3
, running index.js
running index.js
, undefined
, running sum.js
import
命令是编译阶段执行的,在代码运行之前。因此这意味着被导入的模块会先运行,而导入模块的文件会后执行。
这是CommonJS中require()
和import
之间的区别。使用require()
,您可以在运行代码时根据需要加载依赖项。 如果我们使用require
而不是import
,running index.js
,running sum.js
,3
会被依次打印。
console.log(Number(2) === Number(2))
console.log(Boolean(false) === Boolean(false))
console.log(Symbol('foo') === Symbol('foo'))
true
, true
, false
false
, true
, false
true
, false
, true
true
, true
, true
每个Symbol
都是完全唯一的。传递给Symbol
的参数只是给Symbol
的一个描述。 Symbol
的值不依赖于传递的参数。 当我们测试相等时,我们创建了两个全新的符号:第一个Symbol('foo')
,第二个Symbol('foo')
, 这两个值是唯一的,彼此不相等,因此返回false
。
const name = "Lydia Hallie"
console.log(name.padStart(13))
console.log(name.padStart(2))
"Lydia Hallie"
, "Lydia Hallie"
" Lydia Hallie"
, " Lydia Hallie"
("[13x whitespace]Lydia Hallie"
, "[2x whitespace]Lydia Hallie"
)" Lydia Hallie"
, "Lydia Hallie"
("[1x whitespace]Lydia Hallie"
, "Lydia Hallie"
)"Lydia Hallie"
, "Lyd"
使用padStart
方法,我们可以在字符串的开头添加填充。传递给此方法的参数是字符串的总长度(包含填充)。字符串Lydia Hallie
的长度为12
, 因此name.padStart(13)
在字符串的开头只会插入1(13 - 12 = 1
)个空格。
如果传递给padStart
方法的参数小于字符串的长度,则不会添加填充。
console.log("" + "");
""
257548
使用+
运算符,您可以连接字符串。 上述情况,我们将字符串“”
与字符串”“
连接起来,产生”“
。
console.log
语句后注释掉的值?function* startGame() {
const 答案 = yield "Do you love JavaScript?";
if (答案 !== "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("Yes").value
and game.next().value
game.next.value("Yes")
and game.next.value()
game.next().value
and game.next("Yes").value
game.next.value()
and game.next.value("Yes")
generator
函数在遇到yield
关键字时会“暂停”其执行。 首先,我们需要让函数产生字符串Do you love JavaScript?
,这可以通过调用game.next().value
来完成。上述函数的第一行就有一个yield
关键字,那么运行立即停止了,yield
表达式本身没有返回值,或者说总是返回undefined
, 这意味着此时变量 答案
为undefined
next
方法可以带一个参数,该参数会被当作上一个 yield
表达式的返回值。当我们调用game.next("Yes").value
时,先前的 yield
的返回值将被替换为传递给next()
函数的参数"Yes"
。此时变量 答案
被赋值为 "Yes"
,if
语句返回false
,所以JavaScript loves you back ❤️
被打印。
console.log(String.raw`Hello\nworld`);
Hello world!
Hello
world
Hello\nworld
Hello\n
world
String.raw
函数是用来获取一个模板字符串的原始字符串的,它返回一个字符串,其中忽略了转义符(\n
,\v
,\t
等)。但反斜杠可能造成问题,因为你可能会遇到下面这种类似情况:
const path = `C:\Documents\Projects\table.html`
String.raw`${path}`
这将导致:
"C:DocumentsProjects able.html"
直接使用String.raw
String.raw`C:\Documents\Projects\table.html`
它会忽略转义字符并打印:C:\Documents\Projects\table.html
上述情况,字符串是Hello\nworld
被打印出。
async function getData() {
return await Promise.resolve("I made it!");
}
const data = getData();
console.log(data);
"I made it!"
Promise {: "I made it!"}
Promise {}
undefined
异步函数始终返回一个promise。await
仍然需要等待promise的解决:当我们调用getData()
并将其赋值给data
,此时data
为getData
方法返回的一个挂起的promise,该promise并没有解决。
如果我们想要访问已解决的值"I made it!"
,可以在data
上使用.then()
方法:
data.then(res => console.log(res))
这样将打印 "I made it!"
function addToList(item, list) {
return list.push(item);
}
const result = addToList("apple", ["banana"]);
console.log(result);
['apple', 'banana']
2
true
undefined
push()
方法返回新数组的长度。一开始,数组包含一个元素(字符串"banana"
),长度为1。 在数组中添加字符串"apple"
后,长度变为2,并将从addToList
函数返回。
push
方法修改原始数组,如果你想从函数返回数组而不是数组长度,那么应该在push item
之后返回list
。
const box = { x: 10, y: 20 };
Object.freeze(box);
const shape = box;
shape.x = 100;
console.log(shape)
{ x: 100, y: 20 }
{ x: 10, y: 20 }
{ x: 100 }
ReferenceError
Object.freeze
使得无法添加、删除或修改对象的属性(除非属性的值是另一个对象)。
当我们创建变量shape
并将其设置为等于冻结对象box
时,shape
指向的也是冻结对象。你可以使用Object.isFrozen
检查一个对象是否被冻结,上述情况,Object.isFrozen(shape)
将返回true
。
由于shape
被冻结,并且x
的值不是对象,所以我们不能修改属性x
。 x
仍然等于10
,{x:10,y:20}
被打印。
注意,上述例子我们对属性x
进行修改,可能会导致抛出TypeError异常(最常见但不仅限于严格模式下时)。
const { name: myName } = { name: "Lydia" };
console.log(name);
"Lydia"
"myName"
undefined
ReferenceError
当我们从右侧的对象解构属性name
时,我们将其值Lydia
分配给名为myName
的变量。
使用{name:myName}
,我们是在告诉JavaScript我们要创建一个名为myName
的新变量,并且其值是右侧对象的name
属性的值。
当我们尝试打印name
,一个未定义的变量时,就会引发ReferenceError
。
function sum(a, b) {
return a + b;
}
纯函数在相同的输入值时,需产生相同的输出,其输出的结果,与输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
纯函数不会产生副作用。
纯函数与副作用的定义可参考:
https://zh.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)
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
Calculated! 20
Calculated! 20
Calculated! 20
From cache! 20
Calculated! 20
Calculated! 20
From cache! 20
From cache! 20
Calculated! 20
From cache! 20
Error
add
函数是一个记忆函数。 通过记忆化,我们可以缓存函数的结果,以加快其执行速度。上述情况,我们创建一个cache
对象,用于存储先前返回过的值。
如果我们使用相同的参数多次调用addFunction
函数,它首先检查缓存中是否已有该值,如果有,则返回缓存值,这将节省执行时间。如果没有,那么它将计算该值,并存储在缓存中。
我们用相同的值三次调用了addFunction
函数:
在第一次调用,num
等于10
时函数的值尚未缓存,if语句num in cache
返回false
,else块的代码被执行:Calculated! 20
,并且其结果被添加到缓存对象,cache
现在看起来像{10:20}
。
第二次,cache
对象包含10
的返回值。 if语句 num in cache
返回true
,From cache! 20
被打印。
第三次,我们将5 * 2
(值为10)传递给函数。 cache
对象包含10
的返回值。 if语句 num in cache
返回true
,From cache! 20
被打印。
const myLifeSummedUp = ["☕", "", "", ""]
for (let item in myLifeSummedUp) {
console.log(item)
}
for (let item of myLifeSummedUp) {
console.log(item)
}
0
1
2
3
and "☕"
""
""
""
"☕"
""
""
""
and "☕"
""
""
""
"☕"
""
""
""
and 0
1
2
3
0
1
2
3
and {0: "☕", 1: "", 2: "", 3: ""}
通过for-in
循环,我们可以遍历一个对象自有的、继承的、可枚举的、非Symbol的属性。 在数组中,可枚举属性是数组元素的“键”, 即它们的索引。 类似于下面这个对象:
{0: "☕", 1: "", 2: "", 3: ""}
其中键则是可枚举属性,因此 0
,1
,2
,3
被记录。
通过for-of
循环,我们可以迭代可迭代对象(包括 Array
,Map
,Set
,String
,arguments
等)。当我们迭代数组时,在每次迭代中,不同属性的值将被分配给变量item
, 因此“☕”
,“”
,“”
,“”
被打印。
const list = [1 + 2, 1 * 2, 1 / 2]
console.log(list)
["1 + 2", "1 * 2", "1 / 2"]
["12", 2, 0.5]
[3, 2, 0.5]
[1, 1, 1]
数组元素可以包含任何值。 数字,字符串,布尔值,对象,数组,null
,undeifned
, 以及其他表达式,如日期,函数和计算。
元素将等于返回的值。 1 + 2
返回3
,1 * 2
返回’2,'1 / 2
返回0.5
。
function sayHi(name) {
return `Hi there, ${name}`
}
console.log(sayHi())
Hi there,
Hi there, undefined
Hi there, null
ReferenceError
默认情况下,如果不给函数传参,参数的值将为undefined
。 上述情况,我们没有给参数name
传值。 name
等于undefined
,并被打印。
在ES6中,我们可以使用默认参数覆盖此默认的undefined
值。 例如:
function sayHi(name =“Lydia”){...}
在这种情况下,如果我们没有传递值或者如果我们传递undefined
,name
总是等于字符串Lydia
var status = ""
setTimeout(() => {
const status = ""
const data = {
status: "",
getStatus() {
return this.status
}
}
console.log(data.getStatus())
console.log(data.getStatus.call(this))
}, 0)
""
and ""
""
and ""
""
and ""
""
and ""
this
关键字的指向取决于使用它的位置。 在函数中,比如getStatus
,this
指向的是调用它的对象,上述例子中data
对象调用了getStatus
,因此this
指向的就是data
对象。 当我们打印this.status
时,data
对象的status
属性被打印,即""
。
使用call
方法,可以更改this
指向的对象。data.getStatus.call(this)
是将this
的指向由data
对象更改为全局对象。在全局对象上,有一个名为status
的变量,其值为”“
。 因此打印this.status
时,会打印“”
。
const person = {
name: "Lydia",
age: 21
}
let city = person.city
city = "Amsterdam"
console.log(person)
{ name: "Lydia", age: 21 }
{ name: "Lydia", age: 21, city: "Amsterdam" }
{ name: "Lydia", age: 21, city: undefined }
"Amsterdam"
我们将变量city
设置为等于person
对象上名为city
的属性的值。 这个对象上没有名为city
的属性,因此变量city
的值为undefined
。
请注意,我们没有引用person
对象本身,只是将变量city
设置为等于person
对象上city
属性的当前值。
然后,我们将city
设置为等于字符串“Amsterdam”
。 这不会更改person对象:没有对该对象的引用。
因此打印person
对象时,会返回未修改的对象。
function checkAge(age) {
if (age < 18) {
const message = "Sorry, you're too young."
} else {
const message = "Yay! You're old enough!"
}
return message
}
console.log(checkAge(21))
"Sorry, you're too young."
"Yay! You're old enough!"
ReferenceError
undefined
const
和let
声明的变量是具有块级作用域的,块是大括号({}
)之间的任何东西, 即上述情况if / else
语句的花括号。 由于块级作用域,我们无法在声明的块之外引用变量,因此抛出ReferenceError
。
fetch('https://www.website.com/api/user/1')
.then(res => res.json())
.then(res => console.log(res))
fetch
方法的结果fetch
方法的结果.then()
中回调方法返回的结果undefined
第二个.then
中res
的值等于前一个.then
中的回调函数返回的值。 你可以像这样继续链接.then
,将值传递给下一个处理程序。
hasName
设置为true
的方法,前提是不能将true
作为参数传递?function getName(name) {
const hasName = //
}
!!name
name
new Boolean(name)
name.length
使用逻辑非运算符!
,将返回一个布尔值,使用!! name
,我们可以确定name
的值是真的还是假的。 如果name
是真实的,那么!name
返回false
。 !false
返回true
。
通过将hasName
设置为name
,可以将hasName
设置为等于传递给getName
函数的值,而不是布尔值true
。
new Boolean(true)
返回一个对象包装器,而不是布尔值本身。
name.length
返回传递的参数的长度,而不是布尔值true
。
console.log("I want pizza"[0])
"""
"I"
SyntaxError
undefined
可以使用方括号表示法获取字符串中特定索引的字符,字符串中的第一个字符具有索引0,依此类推。 在这种情况下,我们想要得到索引为0的元素,字符'I'
被记录。
请注意,IE7及更低版本不支持此方法。 在这种情况下,应该使用.charAt()
function sum(num1, num2 = num1) {
console.log(num1 + num2)
}
sum(10)
NaN
20
ReferenceError
undefined
您可以将默认参数的值设置为函数的另一个参数,只要另一个参数定义在其之前即可。 我们将值10
传递给sum
函数。 如果sum
函数只接收1个参数,则意味着没有传递num2
的值,这种情况下,num1
的值等于传递的值10
。 num2
的默认值是num1
的值,即10
。 num1 + num2
返回20
。
如果您尝试将默认参数的值设置为后面定义的参数,则可能导致参数的值尚未初始化,从而引发错误。比如:
function test(m = n, n = 2) {
console.log(m, n)
}
test() // Uncaught ReferenceError: Cannot access 'n' before initialization
test(3) // 3 2
test(3, 4) // 3 4
// 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" }
{ default: function default() }
{ default: "Hello world", name: "Lydia" }
module.js
使用import * as name
语法,我们将module.js
文件中所有export
导入到index.js
文件中,并且创建了一个名为data
的新对象。 在module.js
文件中,有两个导出:默认导出和命名导出。 默认导出是一个返回字符串“Hello World”的函数,命名导出是一个名为name
的变量,其值为字符串“Lydia”
。
data
对象具有默认导出的default
属性,其他属性具有指定exports的名称及其对应的值。
class Person {
constructor(name) {
this.name = name
}
}
const member = new Person("John")
console.log(typeof member)
"class"
"function"
"object"
"string"
类是构造函数的语法糖,如果用构造函数的方式来重写Person
类则将是:
function Person() {
this.name = name
}
通过new
来调用构造函数,将会生成构造函数Person
的实例,对实例执行typeof
关键字将返回"object"
,上述情况打印出"object"
。
let newList = [1, 2, 3].push(4)
console.log(newList.push(5))
[1, 2, 3, 4, 5]
[1, 2, 3, 5]
[1, 2, 3, 4]
Error
.push
方法返回数组的长度,而不是数组本身! 通过将newList
设置为[1,2,3].push(4)
,实际上newList
等于数组的新长度:4
。
然后,尝试在newList
上使用.push
方法。 由于newList
是数值4
,抛出TypeError。
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: ...}
{ constructor: ...}
{}
{ constructor: ...}
{ constructor: ...}
{}
{ constructor: ...}
undefined
常规函数,例如giveLydiaPizza
函数,有一个prototype
属性,它是一个带有constructor
属性的对象(原型对象)。 然而,箭头函数,例如giveLydiaChocolate
函数,没有这个prototype
属性。 尝试使用giveLydiaChocolate.prototype
访问prototype
属性时会返回undefined
。
const person = {
name: "Lydia",
age: 21
}
for (const [x, y] of Object.entries(person)) {
console.log(x, y)
}
name
Lydia
and age
21
["name", "Lydia"]
and ["age", 21]
["name", "age"]
and undefined
Error
Object.entries()
方法返回一个给定对象自身可枚举属性的键值对数组,上述情况返回一个二维数组,数组每个元素是一个包含键和值的数组:
[['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
。
function getItems(fruitList, ...args, favoriteFruit) {
return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
["banana", "apple", "pear", "orange"]
[["banana", "apple"], "pear", "orange"]
["banana", "apple", ["pear"], "orange"]
SyntaxError
... args
是剩余参数,剩余参数的值是一个包含所有剩余参数的数组,并且只能作为最后一个参数。上述示例中,剩余参数是第二个参数,这是不可能的,并会抛出语法错误。
function getItems(fruitList, favoriteFruit, ...args) {
return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
上述例子是有效的,将会返回数组:[ 'banana', 'apple', 'orange', 'pear' ]
function nums(a, b) {
if
(a > b)
console.log('a is bigger')
else
console.log('b is bigger')
return
a + b
}
console.log(nums(4, 2))
console.log(nums(1, 2))
a is bigger
, 6
and b is bigger
, 3
a is bigger
, undefined
and b is bigger
, undefined
undefined
and undefined
SyntaxError
在JavaScript中,我们不必显式地编写分号(;
),但是JavaScript引擎仍然在语句之后自动添加分号。这称为自动分号插入。例如,一个语句可以是变量,或者像throw
、return
、break
这样的关键字。
在这里,我们在新的一行上写了一个return
语句和另一个值a + b
。然而,由于它是一个新行,引擎并不知道它实际上是我们想要返回的值。相反,它会在return
后面自动添加分号。你可以这样看:
return;
a + b
这意味着永远不会到达a + b
,因为函数在return
关键字之后停止运行。如果没有返回值,就像这里,函数返回undefined
。注意,在if/else
语句之后没有自动插入!
class Person {
constructor() {
this.name = "Lydia"
}
}
Person = class AnotherPerson {
constructor() {
this.name = "Sarah"
}
}
const member = new Person()
console.log(member.name)
"Lydia"
"Sarah"
Error: cannot redeclare Person
SyntaxError
我们可以将类设置为等于其他类/函数构造函数。 在这种情况下,我们将Person
设置为AnotherPerson
。 这个构造函数的名字是Sarah
,所以新的Person
实例member
上的name属性是Sarah
。
const info = {
[Symbol('a')]: 'b'
}
console.log(info)
console.log(Object.keys(info))
{Symbol('a'): 'b'}
and ["{Symbol('a')"]
{}
and []
{ a: "b" }
and ["a"]
{Symbol('a'): 'b'}
and []
Symbol
类型是不可枚举的。Object.keys
方法返回对象上的所有可枚举的键属性。Symbol
类型是不可见的,并返回一个空数组。 记录整个对象时,所有属性都是可见的,甚至是不可枚举的属性。
这是Symbol
的众多特性之一:除了表示完全唯一的值(防止对象意外名称冲突,例如当使用2个想要向同一对象添加属性的库时),您还可以隐藏
这种方式对象的属性(尽管不完全。你仍然可以使用Object.getOwnPropertySymbols()
方法访问 Symbol
。
const getList = ([x, ...y]) => [x, y]
const getUser = user => { name: user.name, age: user.age }
const list = [1, 2, 3, 4]
const user = { name: "Lydia", age: 21 }
console.log(getList(list))
console.log(getUser(user))
[1, [2, 3, 4]]
and undefined
[1, [2, 3, 4]]
and { name: "Lydia", age: 21 }
[1, 2, 3, 4]
and { name: "Lydia", age: 21 }
Error
and { name: "Lydia", age: 21 }
getList
函数接收一个数组作为其参数。 在getList
函数的括号之间,我们立即解构这个数组。 您可以将其视为:
[x, ...y] = [1, 2, 3, 4]
使用剩余的参数... y
,我们将所有剩余参数放在一个数组中。 在这种情况下,其余的参数是2
,3
和4
。 y
的值是一个数组,包含所有其余参数。 在这种情况下,x
的值等于1
,所以当我们打印[x,y]
时,会打印[1,[2,3,4]]
。
getUser
函数接收一个对象。对于箭头函数,如果只返回一个值,我们不必编写花括号。但是,如果您想从一个箭头函数返回一个对象,您必须在圆括号之间编写它,否则不会返回任何值!下面的函数将返回一个对象:
const getUser = user => ({ name: user.name, age: user.age })
由于在这种情况下不返回任何值,因此该函数返回undefined
。
const name = "Lydia"
console.log(name())
SyntaxError
ReferenceError
TypeError
undefined
变量name
保存字符串的值,该字符串不是函数,因此无法调用。
当值不是预期类型时,会抛出TypeErrors
。 JavaScript期望name
是一个函数,因为我们试图调用它。 但它是一个字符串,因此抛出TypeError
:name is not a function
当你编写了一些非有效的JavaScript时,会抛出语法错误,例如当你把return
这个词写成retrun
时。
当JavaScript无法找到您尝试访问的值的引用时,抛出ReferenceErrors
。
// ✨ This is my 100th question! ✨
const output = `${[] && 'Im'}possible!
You should${'' && `n't`} see a therapist after so much JavaScript lol`
possible! You should see a therapist after so much JavaScript lol
Impossible! You should see a therapist after so much JavaScript lol
possible! You shouldn't see a therapist after so much JavaScript lol
Impossible! You shouldn't see a therapist after so much JavaScript lol
[]
是一个真值。 使用&&
运算符,如果左侧值是真值,则返回右侧值。 在这种情况下,左侧值[]
是一个真值,所以返回Im
。
""
是一个假值。 如果左侧值是假的,则不返回任何内容。 n't
不会被退回。
const one = (false || {} || null)
const two = (null || false || "")
const three = ([] || 0 || true)
console.log(one, two, three)
false
null
[]
null
""
true
{}
""
[]
null
null
true
使用||
运算符,我们可以返回第一个真值。 如果所有值都是假值,则返回最后一个值。
(false || {} || null)
:空对象{}
是一个真值。 这是第一个(也是唯一的)真值,它将被返回。one
等于{}
。
(null || false ||“”)
:所有值都是假值。 这意味着返回传递的值""
。 two
等于""
。
([] || 0 ||“”)
:空数组[]
是一个真值。 这是第一个返回的真值。 three
等于[]
。
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()
I have resolved!
, second
and I have resolved!
, second
second
, I have resolved!
and second
, I have resolved!
I have resolved!
, second
and second
, I have resolved!
second
, I have resolved!
and I have resolved!
, second
有了promise,我们通常会说:当我想要调用某个方法,但是由于它可能需要一段时间,因此暂时将它放在一边。只有当某个值被resolved/rejected,并且执行栈为空时才使用这个值。
我们可以在async
函数中通过.then
和await
关键字获得该值。 尽管我们可以通过.then
和await
获得promise的价值,但是它们的工作方式有所不同。
在 firstFunction
中,当运行到myPromise
方法时我们将其放在一边,即promise进入微任务队列,其他后面的代码(console.log('second')
)照常运行,因此second
被打印出,firstFunction
方法到此执行完毕,执行栈中宏任务队列被清空,此时开始执行微任务队列中的任务,I have resolved
被打印出。
在secondFunction
方法中,我们通过await
关键字,暂停了后面代码的执行,直到异步函数的值被解析才开始后面代码的执行。这意味着,它会等着直到 myPromise
以值I have resolved
被解决之后,下一行second
才开始执行。
const set = new Set()
set.add(1)
set.add("Lydia")
set.add({ name: "Lydia" })
for (let item of set) {
console.log(item + 2)
}
3
, NaN
, NaN
3
, 7
, NaN
3
, Lydia2
, [Object object]2
"12"
, Lydia2
, [Object object]2
“+”运算符不仅用于添加数值,还可以使用它来连接字符串。 每当JavaScript引擎发现一个或多个值不是数字时,就会将数字强制为字符串。
第一个是数字1。 1 + 2返回数字3。
但是,第二个是字符串“Lydia”。 “Lydia”是一个字符串,2是一个数字:2被强制转换为字符串。 “Lydia”和“2”被连接起来,产生字符串“Lydia2”。
{name:“ Lydia”}
是一个对象。 数字和对象都不是字符串,因此将二者都字符串化。 每当我们对常规对象进行字符串化时,它就会变成[Object object]
。 与“2”串联的“ [Object object]”成为“[Object object]2”。
Promise.resolve(5)
5
Promise {: 5}
Promise {: 5}
Error
我们可以将我们想要的任何类型的值传递Promise.resolve
,无论是否promise
。 该方法本身返回带有已解析值的Promise
(
)。 如果您传递常规函数,它将是具有常规值的已解决promise
。 如果你通过了promise,它将是一个已经resolved的且带有传的值的promise。
上述情况,我们传了数字5,因此返回一个resolved状态的promise,resolve值为5
function compareMembers(person1, person2 = person) {
if (person1 !== person2) {
console.log("Not the same!")
} else {
console.log("They are the same!")
}
}
const person = { name: "Lydia" }
compareMembers(person)
Not the same!
They are the same!
ReferenceError
SyntaxError
对象通过引用传递。 当我们检查对象的严格相等性(===)时,我们正在比较它们的引用。
我们将“person2”的默认值设置为“person”对象,并将“person”对象作为“person1”的值传递。
这意味着两个值都引用内存中的同一位置,因此它们是相等的。
运行“ else”语句中的代码块,并记录They are the same!
。
const colorConfig = {
red: true,
blue: false,
green: true,
black: true,
yellow: false,
}
const colors = ["pink", "red", "blue"]
console.log(colorConfig.colors[1])
true
false
undefined
TypeError
在JavaScript中,我们有两种访问对象属性的方法:括号表示法或点表示法。 在此示例中,我们使用点表示法(colorConfig.colors
)代替括号表示法(colorConfig [“ colors”]
)。
使用点表示法,JavaScript会尝试使用该确切名称在对象上查找属性。 在此示例中,JavaScript尝试在colorconfig对象上找到名为colors的属性。 没有名为“colors”的属性,因此返回“undefined”。
然后,我们尝试使用[1]
访问第一个元素的值。 我们无法对未定义的值执行此操作,因此会抛出Cannot read property '1' of undefined
。
JavaScript解释(或取消装箱)语句。 当我们使用方括号表示法时,它会看到第一个左方括号[
并一直进行下去,直到找到右方括号]
。 只有这样,它才会评估该语句。 如果我们使用了colorConfig [colors [1]],它将返回colorConfig对象上red属性的值。
console.log('❤️' === '❤️')
true
false
在内部,表情符号是unicode。 heat表情符号的unicode是“ U + 2764 U + FE0F”
。 对于相同的表情符号,它们总是相同的,因此我们将两个相等的字符串相互比较,这将返回true。
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, '✨')
All of them
map
reduce
slice
splice
map
slice
splice
splice
使用splice
方法,我们通过删除,替换或添加元素来修改原始数组。 在这种情况下,我们从索引1中删除了2个元素(我们删除了''
和''
),同时添加了✨emoji表情。
map
,filter
和slice
返回一个新数组,find
返回一个元素,而reduce
返回一个减小的值。
const food = ['', '', '', '']
const info = { favoriteFood: food[0] }
info.favoriteFood = ''
console.log(food)
['', '', '', '']
['', '', '', '']
['', '', '', '', '']
ReferenceError
我们将info
对象上的favoriteFood
属性的值设置为披萨表情符号“”的字符串。字符串是原始数据类型。在JavaScript中,原始数据类型通过值起作用
在这种情况下,我们将info
对象上的favoriteFood
属性的值设置为等于food
数组中的第一个元素的值,字符串为披萨表情符号(''
)。字符串是原始数据类型,并且通过值进行交互,我们更改info
对象上favoriteFood
属性的值。 food数组没有改变,因为favoriteFood的值只是该数组中第一个元素的值的复制,并且与该元素上的元素没有相同的内存引用食物[0]
。当我们记录食物时,它仍然是原始数组['','','','']
。
JSON.parse()
使用JSON.parse()
方法,我们可以将JSON字符串解析为JavaScript值。
// 将数字字符串化为有效的JSON,然后将JSON字符串解析为JavaScript值:
const jsonNumber = JSON.stringify(4) // '4'
JSON.parse(jsonNumber) // 4
// 将数组值字符串化为有效的JSON,然后将JSON字符串解析为JavaScript值:
const jsonArray = JSON.stringify([1, 2, 3]) // '[1, 2, 3]'
JSON.parse(jsonArray) // [1, 2, 3]
// 将对象字符串化为有效的JSON,然后将JSON字符串解析为JavaScript值:
const jsonArray = JSON.stringify({ name: "Lydia" }) // '{"name":"Lydia"}'
JSON.parse(jsonArray) // { name: 'Lydia' }
let name = 'Lydia'
function getName() {
console.log(name)
let name = 'Sarah'
}
getName()
undefined
ReferenceError
每个函数都有其自己的执行上下文。 getName
函数首先在其自身的上下文(范围)内查找,以查看其是否包含我们尝试访问的变量name
。 上述情况,getName
函数包含其自己的name
变量:我们用let
关键字和Sarah
的值声明变量name
。
带有let
关键字(和const
)的变量被提升,但是与var
不同,它不会被***初始化***。 在我们声明(初始化)它们之前,无法访问它们。 这称为“暂时性死区”。 当我们尝试在声明变量之前访问变量时,JavaScript会抛出ReferenceError: Cannot access 'name' before initialization
。
如果我们不在getName
函数中声明name
变量,则javascript引擎会查看原型练。会找到其外部作用域有一个名为name
的变量,其值为Lydia
。 在这种情况下,它将打印Lydia
:
let name = 'Lydia'
function getName() {
console.log(name)
}
getName() // Lydia
function* generatorOne() {
yield ['a', 'b', 'c'];
}
function* generatorTwo() {
yield* ['a', 'b', 'c'];
}
const one = generatorOne()
const two = generatorTwo()
console.log(one.next().value)
console.log(two.next().value)
a
and a
a
and undefined
['a', 'b', 'c']
and a
a
and ['a', 'b', 'c']
通过 yield
关键字, 我们在 Generator
函数里执行yield
表达式. 通过 yield*
关键字, 我们可以在一个Generator
函数里面执行(yield
表达式)另一个 Generator
函数, 或可遍历的对象 (如数组).
在函数 generatorOne
中, 我们通过 yield
关键字 yield 了一个完整的数组 ['a', 'b', 'c']
。函数one
通过next
方法返回的对象的value
属性的值 (one.next().value
) 等价于数组 ['a', 'b', 'c']
.
console.log(one.next().value) // ['a', 'b', 'c']
console.log(one.next().value) // undefined
在函数 generatorTwo
中, 我们使用 yield*
关键字。就相当于函数two
第一个yield
的值, 等价于在迭代器中第一个 yield
的值。数组['a', 'b', 'c']
就是这个迭代器. 第一个 yield
的值就是 a
, 所以我们第一次调用 two.next().value
时, 就返回a
。
console.log(two.next().value) // 'a'
console.log(two.next().value) // 'b'
console.log(two.next().value) // 'c'
console.log(two.next().value) // undefined
console.log(`${(x => x)('I love')} to program`)
I love to program
undefined to program
${(x => x)('I love') to program
TypeError
带有模板字面量的表达式首先被执行。相当于字符串会包含表达式,这个立即执行函数 (x => x)('I love')
返回的值. 我们向箭头函数 x => x
传递 'I love'
作为参数。x
等价于返回的 'I love'
。这就是结果 I love to program
。
let config = {
alert: setInterval(() => {
console.log('Alert!')
}, 1000)
}
config = null
setInterval
的回调不会被调用setInterval
的回调被调用一次setInterval
的回调仍然会被每秒钟调用config.alert()
, config 为 null
一般情况下当我们将对象赋值为 null
, 那些对象会被进行 垃圾回收(garbage collected) 因为已经没有对这些对象的引用了。然而,setInterval
的参数是一个箭头函数(所以上下文绑定到对象 config
了),回调函数仍然保留着对 config
的引用。只要存在引用,对象就不会被垃圾回收。因为没有被垃圾回收,setInterval
的回调每1000ms (1s)会被调用一次。
'Hello world!'
?const myMap = new Map()
const myFunc = () => 'greeting'
myMap.set(myFunc, 'Hello world!')
//1
myMap.get('greeting')
//2
myMap.get(myFunc)
//3
myMap.get(() => 'greeting')
当通过 set
方法添加一个键值对,一个传递给 set
方法的参数将会是键名,第二个参数将会是值。在这个case里,键名为 函数 () => 'greeting'
,值为'Hello world'
。 myMap
现在就是 { () => 'greeting' => 'Hello world!' }
。
1 是错的,因为键名不是 'greeting'
而是 () => 'greeting'
。
3 是错的,因为我们给get
方法传递了一个新的函数。对象受 引用 影响。函数也是对象,因此两个函数严格上并不等价,尽管他们相同:他们有两个不同的内存引用地址。
const person = {
name: "Lydia",
age: 21
}
const changeAge = (x = { ...person }) => x.age += 1
const changeAgeAndName = (x = { ...person }) => {
x.age += 1
x.name = "Sarah"
}
changeAge(person)
changeAgeAndName()
console.log(person)
{name: "Sarah", age: 22}
{name: "Sarah", age: 23}
{name: "Lydia", age: 22}
{name: "Lydia", age: 23}
函数 changeAge
和函数 changeAgeAndName
有着不同的参数,定义一个 新 生成的对象 { ...person }
。这个对象有着所有 person
对象 中 k/v 值的副本。
首项, 我们调用 changeAge
函数并传递 person
对象作为它的参数。这个函数对 age
属性进行加一操作。person
现在是 { name: "Lydia", age: 22 }
。
然后,我们调用函数 changeAgeAndName
,然而我们没有传递参数。取而代之,x
的值等价 new 生成的对象: { ...person }
。因为它是一个新生成的对象,它并不会对对象 person
造成任何副作用。person
仍然等价于 { name: "Lydia", age: 22 }
。
6
?function sumValues(x, y, z) {
return x + y + z;
}
sumValues([...1, 2, 3])
sumValues([...[1, 2, 3]])
sumValues(...[1, 2, 3])
sumValues([1, 2, 3])
通过展开操作符 ...
,我们可以 暂开 单个可迭代的元素。函数 sumValues
function 接收三个参数: x
, y
和 z
。...[1, 2, 3]
的执行结果为 1, 2, 3
,将会传递给函数 sumValues
。
let num = 1;
const list = ["", "", "", ""];
console.log(list[(num += 1)]);
SyntaxError
ReferenceError
通过 +=
操作符,我们对值 num
进行加 1
操作。 num
有初始值 1
,因此 1 + 1
的执行结果为 2
。数组 list
的第二项为 ,console.log(list[2])
输出 .
const person = {
firstName: "Lydia",
lastName: "Hallie",
pet: {
name: "Mara",
breed: "Dutch Tulip Hound"
},
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
};
console.log(person.pet?.name);
console.log(person.pet?.family?.name);
console.log(person.getFullName?.());
console.log(member.getLastName?.());
undefined
undefined
undefined
undefined
Mara
undefined
Lydia Hallie
undefined
Mara
null
Lydia Hallie
null
null
ReferenceError
null
ReferenceError
通过 ES10 或 TS3.7+可选链操作符 ?.
,我们不再需要显式检测更深层的嵌套值是否有效。如果我们尝试获取 undefined
或 null
的值 (nullish),表达将会短路并返回 undefined
.
person.pet?.name
: person
有一个名为 pet
的属性: person.pet
不是 nullish。它有个名为 name
的属性,并返回字符串 Mara
。
person.pet?.family?.name
: person
有一个名为 pet
的属性: person.pet
不是 nullish. pet
并没有 一个名为 family
的属性, person.pet.family
是 nullish。表达式返回 undefined
。
person.getFullName?.()
: person
有一个名为 getFullName
的属性: person.getFullName()
不是 nullish 并可以被调用,返回字符串 Lydia Hallie
。
member.getLastName?.()
: member
is not defined: member.getLastName()
is nullish. The expression returns undefined
.
const groceries = ["banana", "apple", "peanuts"];
if (groceries.indexOf("banana")) {
console.log("We have to buy bananas!");
} else {
console.log(`We don't have to buy bananas!`);
}
undefined
1
我们传递了一个状态 groceries.indexOf("banana")
给if条件语句。groceries.indexOf("banana")
返回 0
, 一个 falsy 的值。因为if条件语句的状态为 falsy,else
块区内的代码执行,并且 We don't have to buy bananas!
被输出.
const config = {
languages: [],
set language(lang) {
return this.languages.push(lang);
}
};
console.log(config.language);
function language(lang) { this.languages.push(lang }
0
[]
undefined
方法 language
是一个 setter
。Setters 并不保存一个实际值,它们的使命在于 修改 属性。当调用方法 setter
, 返回 undefined
。
const name = "Lydia Hallie";
console.log(!typeof name === "object");
console.log(!typeof name === "string");
false
true
true
false
false
false
true
true
typeof name
返回 "string"
。字符串 "string"
是一个 truthy 的值,因此 !typeof name
返回一个布尔值 false
。 false === "object"
和 false === "string"
都返回 false
。
(如果我们想检测一个值的类型,我们应该用 !==
而不是 !typeof
)
const add = x => y => z => {
console.log(x, y, z);
return x + y + z;
};
add(4)(5)(6);
4
5
6
6
5
4
4
function
function
undefined
undefined
6
函数 add
是一个返回 返回箭头函数的箭头函数 的箭头函数(still with me?)。第一个函数接收一个值为 4
的参数 x
。我们调用第二个函数,它接收一个值为 5
的参数 y
。然后我们调用第三个函数,它接收一个值为 6
的参数 z
。当我们尝试在最后一个箭头函数中获取 x
, y
和 z
的值,JS 引擎根据作用域链去找 x
和 y
的值。得到 4
5
6
.
async function* range(start, end) {
for (let i = start; i <= end; i++) {
yield Promise.resolve(i);
}
}
(async () => {
const gen = range(1, 3);
for await (const item of gen) {
console.log(item);
}
})();
Promise {1}
Promise {2}
Promise {3}
Promise {}
Promise {}
Promise {}
1
2
3
undefined
undefined
undefined
我们给 函数range 传递: Promise{1}
, Promise{2}
, Promise{3}
,Generator 函数 range
返回一个全是 async object promise 数组。我们将 async object 赋值给变量 gen
,之后我们使用for await ... of
进行循环遍历。我们将返回的 Promise 实例赋值给 item
: 第一个返回 Promise{1}
, 第二个返回 Promise{2}
,之后是 Promise{3}
。因为我们正 awaiting item
的值,resolved 状态的 promsie,promise数组的resolved 值 以此为: 1
,2
,3
.
const myFunc = ({ x, y, z }) => {
console.log(x, y, z);
};
myFunc(1, 2, 3);
1
2
3
{1: 1}
{2: 2}
{3: 3}
{ 1: undefined }
undefined
undefined
undefined
undefined
undefined
myFunc
期望接收一个包含 x
, y
和 z
属性的对象作为它的参数。因为我们仅仅传递三个单独的数字值 (1, 2, 3) 而不是一个含有 x
, y
和 z
属性的对象 ({x: 1, y: 2, z: 3}), x
, y
和 z
有着各自的默认值 undefined
.
function getFine(speed, amount) {
const formattedSpeed = new Intl.NumberFormat({
'en-US',
{ style: 'unit', unit: 'mile-per-hour' }
}).format(speed)
const formattedAmount = new Intl.NumberFormat({
'en-US',
{ style: 'currency', currency: 'USD' }
}).format(amount)
return `The driver drove ${formattedSpeed} and has to pay ${formattedAmount}`
}
console.log(getFine(130, 300))
通过方法 Intl.NumberFormat
,我们可以格式化任意区域的数字值。我们对数字值 130
进行 mile-per-hour
作为 unit
的 en-US
区域 格式化,结果为 130 mph
。对数字值 300
进行 USD
作为 currentcy
的 en-US
区域格式化,结果为 $300.00
.
const spookyItems = ["", "", ""];
({ item: spookyItems[3] } = { item: "" });
console.log(spookyItems);
["", "", ""]
["", "", "", ""]
["", "", "", { item: "" }]
["", "", "", "[object Object]"]
通过解构对象们,我们可以从右手边的对象中拆出值,并且将拆出的值分配给左手边对象同名的属性。在这种情况下,我们将值 “” 分配给 spookyItems[3]
。相当于我们正在篡改数组 spookyItems
,我们给它添加了值 “”。当输出 spookyItems
时,结果为 ["", "", "", ""]
。
const name = "Lydia Hallie";
const age = 21;
console.log(Number.isNaN(name));
console.log(Number.isNaN(age));
console.log(isNaN(name));
console.log(isNaN(age));
true
false
true
false
true
false
false
false
false
false
true
false
false
true
false
true
通过方法 Number.isNaN
,你可以检测你传递的值是否为 数字值 并且是否等价于 NaN
。name
不是一个数字值,因此 Number.isNaN(name)
返回 false
。age
是一个数字值,但它不等价于 NaN
,因此 Number.isNaN(age)
返回 false
.
通过方法 isNaN
, 你可以检测你传递的值是否一个 number。name
不是一个 number
,因此 isNaN(name)
返回 true
. age
是一个 number
因此 isNaN(age)
返回 false
.
const randomValue = 21;
function getInfo() {
console.log(typeof randomValue);
const randomValue = "Lydia Hallie";
}
getInfo();
"number"
"string"
undefined
ReferenceError
通过 const
关键字声明的变量在被初始化之前不可被引用:这被称之为 暂时性死区。在函数 getInfo
中, 变量 randomValue
声明在getInfo
的作用域的此法环境中。在想要对 typeof randomValue
进行log之前,变量 randomValue
仍未被初始化: 错误ReferenceError
被抛出! JS引擎并不会根据作用域链网上寻找该变量,因为我们已经在 getInfo
函数中声明了 randomValue
变量。
const myPromise = Promise.resolve("Woah some cool data");
(async () => {
try {
console.log(await myPromise);
} catch {
throw new Error(`Oops didn't work`);
} finally {
console.log("Oh finally!");
}
})();
Woah some cool data
Oh finally!
Woah some cool data
Oh finally!
Oops didn't work
Oh finally!
在 try
块区,我们打印 myPromise
变量的 awaited 值: "Woah some cool data"
。因为try
块区没有错误抛出,catch
块区的代码并不执行。finally
块区的代码 总是 执行,"Oh finally!"
被输出。
const emojis = ["", ["✨", "✨", ["", ""]]];
console.log(emojis.flat(1));
['', ['✨', '✨', ['', '']]]
['', '✨', '✨', ['', '']]
['', ['✨', '✨', '', '']]
['', '✨', '✨', '', '']
通过方法 flat
, 我们可以创建一个新的, 已被扁平化的数组。被扁平化的深度取决于我们传递的值。在这个case里,我们传递了值 1
(并不必要,这是默认值),相当于只有第一层的数组才会被连接。即这个 case 里的 ['']
and ['✨', '✨', ['', '']]
。连接这两个数组得到结果 ['', '✨', '✨', ['', '']]
.
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
}
}
const counterOne = new Counter();
counterOne.increment();
counterOne.increment();
const counterTwo = counterOne;
counterTwo.increment();
console.log(counterOne.count);
0
1
2
3
counterOne
是类 Counter
的一个实例。类 Counter 包含一个count
属性在它的构造函数里, 和一个 increment
方法。首先,我们通过 counterOne.increment()
调用方法 increment
两次。现在, counterOne.count
为 2
.
然后,我们创建一个新的变量 counterTwo
并将 counterOne
的引用地址赋值给它。因为对象受引用地址的影响,我们刚刚创建了一个新的对象,其引用地址和 counterOne
的等价。因此它们指向同一块内存地址,任何对其的副作用都会影响 counterTwo
。现在 counterTwo.count
为 2
。
我们调用 counterTwo.increment()
将 count
的值设为 3
。然后,我们打印 counterOne
里的count,结果为 3
。
const myPromise = Promise.resolve(Promise.resolve("Promise!"));
function funcOne() {
myPromise.then(res => res).then(res => console.log(res));
setTimeout(() => console.log("Timeout!", 0));
console.log("Last line!");
}
async function funcTwo() {
const res = await myPromise;
console.log(await res);
setTimeout(() => console.log("Timeout!", 0));
console.log("Last line!");
}
funcOne();
funcTwo();
Promise! Last line! Promise! Last line! Last line! Promise!
Last line! Timeout! Promise! Last line! Timeout! Promise!
Promise! Last line! Last line! Promise! Timeout! Timeout!
Last line! Promise! Promise! Last line! Timeout! Timeout!
首先,我们调用 funcOne
。在函数 funcOne
的第一行,我们调用myPromise
promise 异步操作。当JS引擎在忙于执行 promise,它继续执行函数 funcOne
。下一行 异步操作 setTimeout
,其回调函数被 Web API 调用。 (详情请参考我关于event loop的文章.)
promise 和 timeout 都是异步操作,函数继续执行当JS引擎忙于执行promise 和 处理 setTimeout
的回调。相当于 Last line!
首先被输出, 因为它不是异步操作。执行完 funcOne
的最后一行,promise 状态转变为 resolved,Promise!
被打印。然而,因为我们调用了 funcTwo()
, 调用栈不为空,setTimeout
的回调仍不能入栈。
我们现在处于 funcTwo
,先 awaiting myPromise。通过 await
关键字, 我们暂停了函数的执行直到 promise 状态变为 resolved (或 rejected)。然后,我们输出 res
的 awaited 值(因为 promise 本身返回一个 promise)。 接着输出 Promise!
。
下一行就是 异步操作 setTimeout
,其回调函数被 Web API 调用。
我们执行到函数 funcTwo
的最后一行,输出 Last line!
。现在,因为 funcTwo
出栈,调用栈为空。在事件队列中等待的回调函数(() => console.log("Timeout!")
from funcOne
, and () => console.log("Timeout!")
from funcTwo
)以此入栈。第一个回调输出 Timeout!
,并出栈。然后,第二个回调输出 Timeout!
,并出栈。得到结果 Last line! Promise! Promise! Last line! Timeout! Timeout!
index.js
中调用 sum.js?
中的 sum
?// sum.js
export default function sum(x) {
return x + x;
}
// index.js
import * as sum from "./sum";
sum(4)
sum.sum(4)
sum.default(4)
*
来导入,只能具名导出
使用符号 *
,我们引入文件中的所有值,包括默认和具名。如果我们有以下文件:
// info.js
export const name = "Lydia";
export const age = 21;
export default "I love JavaScript";
// index.js
import * as info from "./info";
console.log(info);
将会输出以下内容:
{
default: "I love JavaScript",
name: "Lydia",
age: 21
}
以 sum
为例,相当于以下形式引入值 sum
:
{ default: function sum(x) { return x + x } }
我们可以通过调用 sum.default
来调用该函数
const handler = {
set: () => console.log("Added a new property!"),
get: () => console.log("Accessed a property!")
};
const person = new Proxy({}, handler);
person.name = "Lydia";
person.name;
Added a new property!
Accessed a property!
Added a new property!
Accessed a property!
使用 Proxy 对象,我们可以给一个对象添加自定义行为。在这个 case,我们传递一个包含以下属性的对象 handler
: set
and get
。每当我门 设置 属性值时 set
被调用,每当我们 获取 时 get
被调用。
第一个参数是一个空对象 {}
,作为 person
的值。对于这个对象,自定义行为被定义在对象 handler
。如果我们向对象 person
添加属性,set
将被调用。如果我们获取 person
的属性, get
将被调用。
首先,我们向 proxy 对象(person.name = "Lydia"
)添加一个属性 name
。set
被调用并输出 "Added a new property!"
。
然后,我们获取 proxy 对象的一个属性,对象 handler 的属性 get
被调用。输出 "Accessed a property!"
。
person
有副作用?const person = { name: "Lydia Hallie" };
Object.seal(person);
person.name = "Evan Bacon"
person.age = 21
delete person.name
Object.assign(person, { age: 21 })
使用 Object.seal
我们可以防止新属性 被添加,或者存在属性 被移除.
然而,你仍然可以对存在属性进行更改。
person
有副作用?const person = {
name: "Lydia Hallie",
address: {
street: "100 Main St"
}
};
Object.freeze(person);
person.name = "Evan Bacon"
delete person.address
person.address.street = "101 Main St"
person.pet = { name: "Mara" }
使用方法 Object.freeze
对一个对象进行 冻结。不能对属性进行添加,修改,删除。
然而,它仅 对对象进行 浅 冻结,意味着只有 对象中的 直接 属性被冻结。如果属性是另一个 object,像案例中的 address
,address
中的属性没有被冻结,仍然可以被修改。
const add = x => x + x;
function myFunc(num = 2, value = add(num)) {
console.log(num, value);
}
myFunc();
myFunc(3);
2
4
and 3
6
2
NaN
and 3
NaN
2
Error
and 3
6
2
4
and 3
Error
首先我们不传递任何参数调用 myFunc()
。因为我们没有传递参数,num
和 value
获取它们各自的默认值:num 为 2
, 而 value
为函数 add
的返回值。对于函数 add
,我们传递值为2的 num
作为参数。函数 add
返回 4
作为 value
的值。
然后,我们调用 myFunc(3)
并传递值 3
参数 num
的值。我们没有给 value
传递值。因为我们没有给参数 value
传递值,它获取默认值:函数 add
的返回值。对于函数 add
,我们传递值为3的 num
给它。函数 add
返回 6
作为 value
的值。
class Counter {
#number = 10
increment() {
this.#number++
}
getNum() {
return this.#number
}
}
const counter = new Counter()
counter.increment()
console.log(counter.#number)
10
11
undefined
SyntaxError
在 ES2020 中,通过 #
我们可以给 class 添加私有变量。在 class 的外部我们无法获取该值。当我们尝试输出 counter.#number
,语法错误被抛出:我们无法在 class Counter
外部获取它!
const teams = [
{ name: "Team 1", members: ["Paul", "Lisa"] },
{ name: "Team 2", members: ["Laura", "Tim"] }
];
function* getMembers(members) {
for (let i = 0; i < members.length; i++) {
yield members[i];
}
}
function* getTeams(teams) {
for (let i = 0; i < teams.length; i++) {
// ✨ SOMETHING IS MISSING HERE ✨
}
}
const obj = getTeams(teams);
obj.next(); // { value: "Paul", done: false }
obj.next(); // { value: "Lisa", done: false }
yield getMembers(teams[i].members)
yield* getMembers(teams[i].members)
return getMembers(teams[i].members)
return yield getMembers(teams[i].members)
为了遍历 teams
数组中对象的属性 members
中的每一项,我们需要将 teams[i].members
传递给 Generator 函数 getMembers
。Generator 函数返回一个 generator 对象。为了遍历这个 generator 对象中的每一项,我们需要使用 yield*
.
如果我们没有写 yield
,return yield
或者 return
,整个 Generator 函数不会第一时间 return 当我们调用 next
方法.
const person = {
name: "Lydia Hallie",
hobbies: ["coding"]
};
function addHobby(hobby, hobbies = person.hobbies) {
hobbies.push(hobby);
return hobbies;
}
addHobby("running", []);
addHobby("dancing");
addHobby("baking", person.hobbies);
console.log(person.hobbies);
["coding"]
["coding", "dancing"]
["coding", "dancing", "baking"]
["coding", "running", "dancing", "baking"]
函数 addHobby
接受两个参数,hobby
和有着对象 person
中数组 hobbies
默认值的 hobbies
。
首相,我们调用函数 addHobby
,并给 hobby
传递 "running"
以及给 hobbies
传递一个空数组。因为我们给 hobbies
传递了空数组,"running"
被添加到这个空数组。
然后,我们调用函数 addHobby
,并给 hobby
传递 "dancing"
。我们不向 hobbies
传递值,因此它获取其默认值 —— 对象 person
的 属性 hobbies
。我们向数组 person.hobbies
push dancing
。
最后,我们调用函数 addHobby
,并向 hobby
传递 值 "bdaking"
,并且向 hobbies
传递 person.hobbies
。我们向数组 person.hobbies
push dancing
。
pushing dancing
和 baking
之后,person.hobbies
的值为 ["coding", "dancing", "baking"]
class Bird {
constructor() {
console.log("I'm a bird. ");
}
}
class Flamingo extends Bird {
constructor() {
console.log("I'm pink. ");
super();
}
}
const pet = new Flamingo();
I'm pink.
I'm pink.
I'm a bird.
I'm a bird.
I'm pink.
我们创建了类 Flamingo
的实例 pet
。当我们实例化这个实例,Flamingo
中的 constructor
被调用。首相,输出 "I'm pink. "
, 之后我们调用super()
。super()
调用父类的构造函数,Bird
。Bird
的构造函数被调用,并输出 "I'm a bird. "
。
const emojis = ["", "", "", "⭐"];
/* 1 */ emojis.push("");
/* 2 */ emojis.splice(0, 2);
/* 3 */ emojis = [...emojis, ""];
/* 4 */ emojis.length = 0;
const
关键字意味着我们不能 重定义 变量中的值,它 仅可读。而然,值本身不可修改。数组 emojis
中的值可被修改,如 push 新的值, 拼接,又或者将数组的长度设置为0。
person
添加什么,以致执行 [...person]
时获得形如 ["Lydia Hallie", 21]
的输出?const person = {
name: "Lydia Hallie",
age: 21
}
[...person] // ["Lydia Hallie", 21]
*[Symbol.iterator]() { for (let x in this) yield* this[x] }
*[Symbol.iterator]() { for (let x in this) yield* Object.values(this) }
*[Symbol.iterator]() { for (let x in this) yield this }
对象默认并不是可迭代的。如果迭代规则被定义,则一个对象是可迭代的(An iterable is an iterable if the iterator protocol is present)。我们可以通过添加迭代器symbol [Symbol.iterator]
来定义迭代规则,其返回一个 generator 对象,比如说构建一个 generator 函数 *[Symbol.iterator]() {}
。如果我们想要返回数组 ["Lydia Hallie", 21]
: yield* Object.values(this)
,这个 generator 函数一定要 yield 对象 person
的Object.values
。
let count = 0;
const nums = [0, 1, 2, 3];
nums.forEach(num => {
if (num) count += 1
})
console.log(count)
在 forEach
循环内部的 if
会判断 num
的值是truthy或者是falsy。因为 nums
数组的第一个数字是 0
,一个falsy值, if
语句代码块不会被执行。count
仅仅在 nums
数组的其他3个数字 1
,2
,3
时加1。因为 count
执行了3次加 1
运算,所以 count
的值为 3
。
function getFruit(fruits) {
console.log(fruits?.[1]?.[1])
}
getFruit([['', ''], ['']])
getFruit()
getFruit([[''], ['', '']])
null
, undefined
, []
, null
, []
, []
, undefined
, undefined
,
?
允许我们去选择性地访问对象内部更深层的嵌套属性。 我们尝试打印 fruits
数组索引值为 1
的子数组内部的索引值为 1
的元素。 如果在 fruits
数组索引值 为 1
的位置不存在元素,会直接返回 undefined
。 如果 fruits
数组在索引值为 1
的位置存在元素,但是子数组在索引值为 1
的位置不存在元素,也会返回 undefined
。
首先,我们尝试打印 [['', ''], ['']]
的子数组 ['']
的第2个元素。这个子数组只包含一个元素,也就意味着在索引值为 1
的位置不存在元素,所以返回的是 undefined
。
其次,我们在没有传入任何参数调用了 getFruits
函数,也就意味着形参 fruits
的默认值为undefined
。因为我们选择性地链接了 fruits
在索引值为 1
的元素,因为在索引值为 1
的位置不存在元素,因此返回的是 undefined
。
最后,我们尝试打印 [''], ['', '']
的子数组 ['', '']
的第2个元素。子数组索引值为 1
的位置为 ,因此它被打印出了。
class Calc {
constructor() {
this.count = 0
}
increase() {
this.count ++
}
}
const calc = new Calc()
new Calc().increase()
console.log(calc.count)
0
1
undefined
ReferenceError
我们设置 calc
变量为 Calc
类的一个新实例。 然后,我们初始化一个 Calc
的新实例,而且调用了这个实例的 increase
方法。因为count属性是在 Calc
class的constructor内部的,所以count属性不会在 Calc
的原型链上共享出去。这就意味着calc实例的count值不会被更新,count仍然是 0
。
const user = {
email: "[email protected]",
password: "12345"
}
const updateUser = ({ email, password }) => {
if (email) {
Object.assign(user, { email })
}
if (password) {
user.password = password
}
return user
}
const updatedUser = updateUser({ email: "[email protected]" })
console.log(updatedUser === user)
false
true
TypeError
ReferenceError
updateUser
函数更新user的 email
和 password
属性的值, 如果它们的值传入函数, 函数返回的就是 user
对象。 updateUser
函数的返回值是 user
对象,意味着updatedUser的值与 user
指向的是同一个 user
对象。updatedUser === user
为 true
.
const fruit = ['', '', '']
fruit.slice(0, 1)
fruit.splice(0, 1)
fruit.unshift('')
console.log(fruit)
['', '', '']
['', '']
['', '', '']
['', '', '', '']
首先,我们在fruit数组上调用 slice
方法。 slice方法不会修改原始数组,但是会返回从数组切片下来的值:香蕉emoji。
其次,我们在fruit数组上调用 splice
方法。 splice方法会修改原始数组,也就意味着fruit数组此时为 ['', '']
。
最后,我们在fruit数组上调用 unshift
方法,通过添加一个值的方式改变了原始数组,添加的是’’,它成为了数组的第一个元素。现在fruit数组的组成为 ['', '', '']
。
const animals = {};
let dog = { emoji: '' }
let cat = { emoji: '' }
animals[dog] = { ...dog, name: "Mara" }
animals[cat] = { ...cat, name: "Sara" }
console.log(animals[dog])
{ emoji: "", name: "Mara" }
{ emoji: "", name: "Sara" }
undefined
ReferenceError
对象的键会被转换为字符串。
因为 dog
的值是一个对象, animals[dog]
实际上意味着我们创建了一个叫做 "object Object"
的属性来代表新的对象。 animals["object Object"]
现在等于 { emoji: "", name: "Mara"}
。
cat
也是一个对象,animals[cat]
实际上意味着我们在用新的cat的属性覆盖 animals[``"``object Object``"``]
的值。
打印 animals[dog]
,实际上是animals["object Object"]
,这是因为转化dog
对象为一个字符串结果 "object Object"
,所以返回 { emoji: "", name: "Sara" }
。
const user = {
email: "[email protected]",
updateEmail: email => {
this.email = email
}
}
user.updateEmail("[email protected]")
console.log(user.email)
[email protected]
[email protected]
undefined
ReferenceError
updateEmail
函数是一个箭头函数,它没有和 user
对象绑定。这就意味着 this
关键字不会引用到 user
对象,但是会引用到全局对象。 user
对象内部的 email
的值不会更新。当打印 user.email
的时候, 原始值 [email protected]
被返回。
const promise1 = Promise.resolve('First')
const promise2 = Promise.resolve('Second')
const promise3 = Promise.reject('Third')
const promise4 = Promise.resolve('Fourth')
const runPromises = async () => {
const res1 = await Promise.all([promise1, promise2])
const res2 = await Promise.all([promise3, promise4])
return [res1, res2]
}
runPromises()
.then(res => console.log(res))
.catch(err => console.log(err))
[['First', 'Second'], ['Fourth']]
[['First', 'Second'], ['Third', 'Fourth']]
[['First', 'Second']]
'Third'
Promise.all
方法可以并行式运行promise。如果其中一个promise失败了,Promise.all
方法会带上被reject的promise的值_rejects_。在这个例子中, promise3
带着 "Third"
值reject。我们在调用 runPromises
时在 runPromises
函数内部的 catch
方法去捕获任意error从而捕获到被reject的值。因为 promise3
带着 "Third"
被reject,所以只有 "Third"
打印。
method
的值可以打印{ name: "Lydia", age: 22 }
?const keys = ["name", "age"]
const values = ["Lydia", 22]
const method = /* ?? */
Object[method](keys.map((_, i) => {
return [keys[i], values[i]]
})) // { name: "Lydia", age: 22 }
entries
values
fromEntries
forEach
fromEntries
方法可以将二维数组转换为对象。在每个子数组的第一个元素是key,在每个子数组的第二个元素是value。在这个例子中,我们映射了 keys
数组,它返回了一个数组,数组的第一个元素为keys数组当前索引的值,第二个元素为values数组当前索引的值。
这样就创建了一个包含正确keys和values的子数组的数组,因此结果为{ name: "Lydia", age: 22 }
。
const createMember = ({ email, address = {}}) => {
const validEmail = /.+\@.+\..+/.test(email)
if (!validEmail) throw new Error("Valid email pls")
return {
email,
address: address ? address : null
}
}
const member = createMember({ email: "[email protected]" })
console.log(member)
{ email: "[email protected]", address: null }
{ email: "[email protected]" }
{ email: "[email protected]", address: {} }
{ email: "[email protected]", address: undefined }
address
的默认值是一个空对象 {}
。当我们设置 member
变量为 createMember
函数返回的对象,我们没有为address参数传值,意味着address的值为默认的空对象 {}
。一个空对象是一个truthy值,意味着 address ? address : null
条件会返回 true
。address的值为空对象 {}
。
let randomValue = { name: "Lydia" }
randomValue = 23
if (!typeof randomValue === "string") {
console.log("It's not a string!")
} else {
console.log("Yay it's a string!")
}
It's not a string!
Yay it's a string!
TypeError
undefined
if
语句的条件判断 !typeof randomValue
的值是否等于 "string"
。 !
操作符将这个值转化为一个布尔值。如果值是truthy的话,返回值会是 false
,如果值是falsy,返回值会是 true
。在这里, typeof randomValue
的返回值是一个truthy值 "number"
,意味着 !typeof randomValue
的值是一个布尔值 false
。
!typeof randomValue === "string"
总是返回false,因为我们实际上是在执行 false === "string"
。因为条件返回的是 false
,所以 else
语句中的代码块会被运行,因此打印 Yay it's a string!
。