在函数内部,我们首先通过 var 关键字声明了 name 变量。这意味着变量被提升了(内存空间在创建阶段就被设置好了),直到程序运行到定义变量位置之前默认值都是 undefined。因为当我们打印 name 变量时还没有执行到定义变量的位置,因此变量的值保持为 undefined。
通过 let 和 const 关键字声明的变量也会提升,但是和 var 不同,它们不会被初始化。在我们声明(初始化)之前是不能访问它们的。这个行为被称之为暂时性死区。当我们试图在声明之前访问它们时,JavaScript 将会抛出一个 ReferenceError 错误。
var 能重复声明
function sayHi() {
console.log(name) //undefined
console.log(age) //ReferenceError: Cannot access 'age' before initialization
var name = 'Lydia'
let age = 21
}
sayHi()
由于 JavaScript 的事件循环,setTimeout 回调会在遍历结束后才执行。因为在第一个遍历中遍历 i 是通过 var 关键字声明的,所以这个值是全局作用域下的。在遍历过程中,我们通过一元操作符 ++ 来每次递增 i 的值。当 setTimeout 回调执行的时候,i 的值等于 3。 在第二个遍历中,遍历 i 是通过 let 关键字声明的:通过 let 和 const 关键字声明的变量是拥有块级作用域(指的是任何在 {} 中的内容)。在每次的遍历过程中,i 都有一个新值,并且每个值都在循环内的作用域中。
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1) //3 3 3
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1) //0 1 2
}
const shape = {
radius: 10,
diameter() {
return this.radius * 2 // this 指向当前对象
},
perimeter: () => 2 * Math.PI * this.radius // this 指向外层作用域(非实例)
}
console.log(shape.diameter()) // 20
console.log(shape.perimeter()) // NaN
console.log(+true, !'Lydia') // 1 false
一元操作符加号尝试将 bool 转为 number。true 转换为 number 的话为 1,false 为 0。 字符串 'Lydia' 是一个真值,真值取反那么就返回 false。
const bird = {
size: 'small'
}
const mouse = {
name: 'Mickey',
small: true
}
console.log(mouse[bird['size']]) //true
console.log(mouse[bird.size]) //true
console.log(mouse.bird.size) // undefined
let a = 3
let b = new Number(3)
let c = 3
console.log(a == b) // true
console.log(a === b) // false
console.log(b === c) // false
console.log(1 == '1') // true
console.log(1 === '1') // false
静态方法被设计为只能被创建它们的构造器使用(也就是 Chameleon),并且不能传递给实例
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
function bark() {
console.log('Woof!')
}
bark.animal = 'dog'
bark()
console.log(bark.animal) // Output: dog
function Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
const member = new Person('Lydia', 'Hallie')
Person.getName = function () {
return `${this.firstName} ${this.lastName}`
}
Person.prototype.getFullName = function () {
return `${this.firstName} ${this.lastName}`
}
console.log(member.getFullName()) //Lydia Hallie
console.log(member.getName()) //TypeError: member.getName is not a function
当未使用 new 时,this 引用的是全局对象(global object)
function Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
const lydia = new Person('Lydia', 'Hallie')
console.log('11', lydia) //Person {firstName: 'Lydia', lastName: 'Hallie'}
const sarah = Person('Sarah', 'Smith')
console.log('22', sarah) // undefined
后置自增(x++):「先用后加」 前置自增(++x):「先加后用」
let number = 0
console.log(number++) // 0
console.log(++number) // 2
console.log(number) // 2
而数组是对象,因此 typeof args 返回 "object"。
function getAge(...args) {
console.log(typeof args) // object
}
getAge(21)
将传入的字符串当作 JavaScript 代码来解析并执行,并返回执行结果
let x = 10
eval('x = 20; console.log("执行了eval内部代码")')
console.log(x) // 20(当前作用域的x被修改了)
eval(`
let a = 1;
let b = 2;
function add() { return a + b; }
`)
console.log(add()) // 3(eval定义的函数在当前作用域可访问)
风险:
const sum = '10*10+5'
console.log(sum) // 10*10+5
const sum2 = eval('10*10+5')
console.log(sum2) // 105
所有对象的键(不包括 Symbol)在底层都是字符串,即使你自己没有将其作为字符串输入。这就是为什么 obj.hasOwnProperty('1') 也返回 true。 对于集合,它不是这样工作的。在我们的集合中没有 '1':set.has('1') 返回 false。它有数字类型为 1,set.has(1) 返回 true。
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)
const obj = { a: 'one', b: 'two', a: 'three' }
console.log(obj) //{a: 'three', b: 'two'}
如果某个条件返回 true,则 continue 语句跳过本次迭代。
for (let i = 1; i < 5; i++) {
if (i === 3) continue
console.log(i) //1 2 4
}
for (let i = 1; i < 5; i++) {
if (i === 3) break
console.log(i) //1 2
}
.call 是立即执行的。 .bind 返回函数的副本,但带有绑定上下文!它不是立即执行的。
const person = { name: 'Lydia' }
function sayHi(age) {
console.log(`${this.name} is ${age}`)
}
sayHi.call(person, 21) // Lydia is 21
sayHi.bind(person, 21)
// falsy 值的判断(都会走 else)
if (false) console.log('执行')
else console.log('不执行') // 不执行
if (0) console.log('执行')
else console.log('不执行') // 不执行
if ('') console.log('执行')
else console.log('不执行') // 不执行
if (null) console.log('执行')
else console.log('不执行') // 不执行
if (undefined) console.log('执行')
else console.log('不执行') // 不执行
if (NaN) console.log('执行')
else console.log('不执行') // 不执行
// truthy 值的判断(都会走 if)
if (true) console.log('执行')
else console.log('不执行') // 执行
if (1) console.log('执行')
else console.log('不执行') // 执行
if (' ') console.log('执行')
else console.log('不执行') // 执行(含空格的字符串)
if ({}) console.log('执行')
else console.log('不执行') // 执行(空对象)
if ([]) console.log('执行')
else console.log('不执行') // 执行(空数组)
;(() => {
let x, y
try {
throw new Error()
} catch (x) {
;(x = 1), (y = 2) //x变成了catch块的局部变量,而不是全局变量
console.log(x)
}
console.log(x)
console.log(y)
})()
在第一次执行时, acc 的值是 [1, 2], cur 的值是 [0, 1]。合并它们,结果为 [1, 2, 0, 1]。 第二次执行, acc 的值是 [1, 2, 0, 1], cur 的值是 [2, 3]。合并它们,最终结果为 [1, 2, 0, 1, 2, 3]
const arr = [
[0, 1],
[2, 3]
].reduce(
(acc, cur) => {
return acc.concat(cur)
},
[1, 2] // 初始值
)
console.log(arr) // [1, 2, 0, 1, 2, 3]
function* generator(i) {
yield i
yield i * 2
}
const gen = generator(10)
console.log(gen.next().value) //10
console.log(gen.next().value) //20
let person = { name: 'Lydia' }
const members = [person]
person = null
console.log(members)
对原型也是适用的。删除了原型的属性后,该属性在原型链上就不可用了。
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()
引入的模块是 只读 的: 你不能修改引入的模块。只有导出他们的模块才能修改其值。 当我们给 myCounter 增加一个值的时候会抛出一个异常: myCounter 是只读的,不能被修改。
// counter.js
let counter = 10
export default counter
// index.js
import myCounter from './counter'
myCounter += 1
console.log(myCounter)
Object.defineProperty 添加属性时,默认 enumerable: false(不可枚举),而 Object.keys() 只返回可枚举属性的键名。因此,age 因为不可枚举,没有被 Object.keys(person) 包含,只显示了默认可枚举的 name 属性。
const person = { name: 'Lydia' }
Object.defineProperty(person, 'age', { value: 21 })
console.log(person) //{name: 'Lydia', age: 21}
console.log(Object.keys(person)) //['name']
JSON.stringify 的第二个参数是 替代者(replacer). 替代者(replacer)可以是个函数或数组,用以控制哪些值如何被转换为字符串。
const settings = {
username: 'lydiahallie',
level: 19,
health: 90
}
const data = JSON.stringify(settings, ['level', 'health'])
console.log(data) //{"level":19,"health":90}
console.log(Number(2) === Number(2)) //true
console.log(Boolean(false) === Boolean(false)) //true
console.log(Symbol('foo') === Symbol('foo')) //false
const info = {
[Symbol('a')]: 'b'
}
console.log(info) //{Symbol(a): 'b'}
console.log(Object.keys(info)) //[]
function addToList(item, list) {
return list.push(item) // 2
// return list //['banana', 'apple']
}
const result = addToList('apple', ['banana'])
console.log(result)
fetch('https://www.website.com/api/user/1')
.then(res => res.json())
.then(res => console.log(res))
前一个.then()中回调方法返回的结果 第二个.then 中 res 的值等于前一个.then 中的回调函数返回的值。 你可以像这样继续链接.then,将值传递给下一个处理程序。
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)) //[1, [2, 3, 4]]
console.log(getUser(user)) //{ name: "Lydia", age: 21 }
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() //second , I have resolved!
secondFunction() //I have resolved!, second
const add = x => y => z => {
console.log(x, y, z)
return x + y + z
}
const res = add(4)(5)(6)
console.log(res) // Output: 15
输出:button
在事件传播过程中,有 3 个阶段:捕获、目标和冒泡。
默认情况下,事件处理程序在冒泡阶段执行(除非将 useCapture 设置为 true),它从最深的嵌套元素向外。
<div onclick="console.log('first div')">
<div onclick="console.log('second div')">
<button onclick="console.log('button')">Click!</button>
</div>
</div>