know

编程题

# 1. var 变量提升,let\const 暂时性死区

在函数内部,我们首先通过 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()

# 2. for 循环中的 var 和 let

由于 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
}

# 3. class 中箭头函数的指向

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

# 4. 一元操作符加号转换

console.log(+true, !'Lydia') // 1 false

一元操作符加号尝试将 bool 转为 number。true 转换为 number 的话为 1,false 为 0。 字符串 'Lydia' 是一个真值,真值取反那么就返回 false。

# 5. 对象属性的访问方式以及属性是否存在

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

# 6. 相等运算符和严格相等运算符

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

# 7. static 方法

静态方法被设计为只能被创建它们的构造器使用(也就是 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

# 8. js 除了基本类型之外其他都是对象

function bark() {
  console.log('Woof!')
}

bark.animal = 'dog'
bark()
console.log(bark.animal) // Output: dog

# 9. 给构造函数添加属性和方法,应该使用原型

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

# 10. this 指向

当未使用 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

# 11. 一元后自增运算符 ++

后置自增(x++):「先用后加」 前置自增(++x):「先加后用」

let number = 0
console.log(number++) // 0
console.log(++number) // 2
console.log(number) // 2

# 12. 扩展运算符(...args)会返回实参组成的数组

而数组是对象,因此 typeof args 返回 "object"。

function getAge(...args) {
  console.log(typeof args) // object
}

getAge(21)

# 13. eval 函数

将传入的字符串当作 JavaScript 代码来解析并执行,并返回执行结果

  1. 它会在当前作用域中执行代码,可能会修改当前作用域的变量
    let x = 10
    eval('x = 20; console.log("执行了eval内部代码")')
    console.log(x) // 20(当前作用域的x被修改了)
    
  2. 不仅能执行表达式,还能执行任意 JavaScript 语句(如循环、函数定义等):
    eval(`
    let a = 1;
    let b = 2;
    function add() { return a + b; }
    `)
    console.log(add()) // 3(eval定义的函数在当前作用域可访问)
    

风险:

  1. 容易导致代码执行的混乱,容易导致 XSS 攻击
  2. 容易导致代码执行的性能问题,因为它会在每次执行时都要重新解析代码,导致性能下降
  3. 容易导致代码执行的安全问题,因为它可以执行任意的 JavaScript 代码,可能会对系统造成危害
const sum = '10*10+5'
console.log(sum) // 10*10+5

const sum2 = eval('10*10+5')
console.log(sum2) // 105

# 14. hasOwnPropert 和 set.has 的区别

所有对象的键(不包括 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)

# 15. 对象相同 key 的覆盖

const obj = { a: 'one', b: 'two', a: 'three' }
console.log(obj) //{a: 'three', b: 'two'}

# 16. 循环的 continue,break

如果某个条件返回 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
}

# 17. .call 和 bind 的区别

.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)

# 18. Falsy(虚值)和 Truthy(真值)

  1. falsy ≠ false:false 是 falsy 值的一种,但 falsy 值还包括 0、"" 等其他值。
  2. 空对象 {}、空数组 [] 是 truthy:很多人会误以为它们是 falsy,其实它们是对象,属于 truthy。
  3. 字符串 "false" 是 truthy:它是一个非空字符串,不会被当作布尔值 false 处理。
// 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('不执行') // 执行(空数组)

# 19. 传递参数时候改变作用域

;(() => {
  let x, y
  try {
    throw new Error()
  } catch (x) {
    ;(x = 1), (y = 2) //x变成了catch块的局部变量,而不是全局变量
    console.log(x)
  }
  console.log(x)
  console.log(y)
})()

# 20. reduce() 方法

在第一次执行时, 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]

# 21. generator 的 yield

function* generator(i) {
  yield i
  yield i * 2
}

const gen = generator(10)

console.log(gen.next().value) //10
console.log(gen.next().value) //20

# 22. 变量引用和复制

let person = { name: 'Lydia' }
const members = [person]
person = null

console.log(members)

# 23. delete 关键字删除对象的属性

对原型也是适用的。删除了原型的属性后,该属性在原型链上就不可用了。

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()

# 24. 模块引入

引入的模块是 只读 的: 你不能修改引入的模块。只有导出他们的模块才能修改其值。 当我们给 myCounter 增加一个值的时候会抛出一个异常: myCounter 是只读的,不能被修改。

// counter.js
let counter = 10
export default counter

// index.js
import myCounter from './counter'
myCounter += 1
console.log(myCounter)

# 25. Object.defineProperty

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']

# 26. JSON.stringify 的第二个参数

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}

# 27. 每个 Symbol 都是完全唯一的

console.log(Number(2) === Number(2)) //true
console.log(Boolean(false) === Boolean(false)) //true
console.log(Symbol('foo') === Symbol('foo')) //false

# 27.1. Symbol 类型是不可枚举的

const info = {
  [Symbol('a')]: 'b'
}

console.log(info) //{Symbol(a): 'b'}
console.log(Object.keys(info)) //[]

# 28. push()方法返回新数组的长度

function addToList(item, list) {
  return list.push(item) // 2
  // return list  //['banana', 'apple']
}

const result = addToList('apple', ['banana'])
console.log(result)

# 29. fetch 方法的结果

fetch('https://www.website.com/api/user/1')
  .then(res => res.json())
  .then(res => console.log(res))

前一个.then()中回调方法返回的结果 第二个.then 中 res 的值等于前一个.then 中的回调函数返回的值。 你可以像这样继续链接.then,将值传递给下一个处理程序。

# 30. 解构

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 }

# 31. 宏任务和微任务考察

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

# 32. 箭头函数

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

# 33. 在点击 button 时,触发的 event.target 是哪个?

输出: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>
上次更新: