面试题

收藏

一、

1ff5d556234a98338635ab5f140e71b.png112f2314b56f2186ad4d034efadeb44.png

二、

var a = 0,
  b = 0
function A(a) {
  A = function (b) {
    console.log(a + b++)
  }
  console.log(a++) // 第一次执行到这的时候,变量a在内存中的值已经为2
}
A(1)
A(2)

// 1.首先,全局变量'a'和'b'被初始化为0

// 2.调用函数A(1),进入函数体内:

/**

 * (1)传入参数 a 为1,

 * (2)函数A被重新赋值为一个新的函数

 * (3)在函数内部,打印输出a++,此时打印出的a为1,然后进行a++,在内存中a的值为2

 */

// 3.调用函数A(2),进入函数体内:

/**

 * (1)A函数已被定义,此时执行的是新定义的函数 function(b)

 * (2)传入b的值为2

 * (3)在新定义的函数内部,计算并输出a+b++,其中a为之前的参数2,b是2(传入的参数)

 * (4)打印出a+b++的结果为4,b再执行自增,最终b的值变成3

 */

/**

 * (1)变量作用域和提升: JavaScript 中的变量有不同的作用域,包括函数作用域和全局作用域。

 *      在函数内部重新定义变量会覆盖外部的同名变量。此外,变量和函数在执行之前会被提升到作用域的顶部。

 *

 * (2)函数声明和函数表达式: JavaScript 中可以使用函数声明和函数表达式来创建函数。函数声明会被提升,

 *    而函数表达式则不会。在这个题目中,函数 A 被反复赋值,所以会导致不同的行为。

 *

 * (3)闭包: 在 JavaScript 中,函数可以访问其外部作用域中的变量。在这个题目中,函数 A 的内部函数(也叫闭包)

 *    会保留对外部变量 a 的引用,即使 A 函数被重新赋值,闭包中的引用仍然存在。

 *

 * (4)参数传递和自增运算: 函数可以接受参数,并在内部进行操作。自增运算符 ++ 会增加变量的值。

 *

 * (5)函数调用和执行顺序: 函数按照调用的顺序依次执行,但由于 JavaScript 的事件循环机制,异步操作可能会导致某些代码在其他代码之前执行。

 */

三、

/**

 * 1.'.'和'='的优先级

 */

let a = { n: 1 }
let b = a // 浅拷贝,a、b指向同一个堆内存,a、b引用同一个对象{n:1}
a.x = a = { n: 2 } // a = {n:2}
console.log(a.x)
console.log(b.x)

/**

 * 这段代码涉及了JavaScript中的赋值、引用、以及操作符优先级等概念

 *

 * 第一步: let a = { n: 1 }: 创建了一个名为a的变量,并将一个对象{ n: 1 }赋值给它。

 *

 * 第二步: let b = a: 创建了一个名为b的变量,并将变量a的引用赋值给它,所以b和a引用同一个对象 { n: 1 }。

 *

 * a.x = a = { n: 2 }:

 * 在这儿涉及到运算符的优先级:'.' 的优先级比 '=' 高

 *

 * 第三步:所以,a.x,此时a、b的结构都为:

 * {

 *   n:1,

 *   x:undefined

 * },a、b还是都引用这一个对象,取名对象A

 *

 * 第四步,然后,'=' 运算符从右往左,a就变成了{ n:2 }

 *

 * 此时,栈内存中a、b两个变量,a指向堆内存中的对象B:{ n:2 },b指向堆内存中的对象A: { n:1, x:undefined }

 *

 * 第五步,接下来,a.x = a,此时,这儿可以理解为对象A的属性x指向了对象B

 *

 * 当console.log(a.x)的时候,a是指向对象B的,但对象B没有属性x。当查找一个对象的属性时,JavaScript 会向上遍历原型链,

 * 直到找到给定名称的属性为止。但当查找到达原型链的顶部 - 也就是 Object.prototype - 仍然没有找到指定的属性B.prototype.x,

 * 自然也就输出undefined。

 * 而在console.log(b.x)的时候,由于b.x表示对象A的x属性,该属性是指向对象B,自然也输出{ n: 2 }

 *

 * 所以,最终

 * undefined

 * { n: 2 }

四、

const numbers = [1, 2, 3]
numbers[10] = 11
console.log(numbers)

 *

const numbers = [1, 2, 3]
numbers[10] = 11
console.log(numbers)

/**

 * 例题解析:

 *

 * 在这段代码中,你首先创建了一个名为 numbers 的数组,其中包含了三个元素 [1, 2, 3]。

 * 然后,你尝试将数组的第 10 个位置(索引为 10)设置为 11。

 * 这会导致数组中间隔了多个未定义的元素,因为索引 3 到 9 之间的元素都会被隐式地设置为 undefined。

 * 最终,你将整个数组打印出来。

 */

/**

 *

 * 在这个输出中,<7 empty items> 表示有 7 个未定义的空元素。这是因为 JavaScript 数组是稀疏的,它们可以包含未定义的元素,而不需要在数组中依次填充每个索引。

 *

 */

/**

 * 扩展知识点:稀疏数组

 *

 *    稀疏数组(Sparse Array)是指数组中包含未定义(或者说是空)元素的数组。

 *

 *    在 JavaScript 中,数组是一个有序的集合,每个元素通过一个索引来访问。正常情况下,数组的索引是连续的,从 0 开始递增,依次排列。

 *    但是,JavaScript 中的数组允许存在不连续的索引,即某些位置上没有对应的元素。

 *

 *    在稀疏数组中,数组的某些索引位置没有存储实际的元素值,而是被标记为“空”。这就导致数组的长度(由 length 属性表示)可能比实际存储的元素数量要大得多。

 *

 *    稀疏数组可以出现在一些特殊情况下,例如:

 *

 *    直接设置元素: 如果你在一个数组中设置了一个非连续的索引位置的元素,那么这个索引之前的位置都会被视为稀疏。

 *    删除元素: 删除数组中的某个元素,并不会影响数组的长度,导致被删除元素的位置变为空。

 *    使用 delete 关键字: 使用 delete 关键字删除数组的某个元素,同样会使数组变得稀疏。

 *

 * **重点**:稀疏数组在某些情况下可能会导致不太直观的行为,因为使用 for...in 循环或 forEach 等迭代方法时,可能会跳过空的索引位置。

 */

/**

 * (1)通过for循环遍历例题中的numbers数组:

 */

// for (i = 0; i < numbers.length; i++) {

//   console.log(numbers[i])

// }

/**

 * 结果为:1

 *        2

 *        3

 *        undefined

 *        undefined

 *        undefined

 *        undefined

 *        undefined

 *        undefined

 *        undefined

 *        11

 *

 */

/**

 * (2)通过forEach遍历

 */

// numbers.forEach((item) => {

//   console.log(item)

// })

/**

 * 结果为:1

 *        2

 *        3

 *        11

 */

/**

 * (3)for...of循环

 */

// for (const item of numbers) {

//   console.log(item)

// }

/**

 * 结果为:1

 *        2

 *        3

 *        undefined

 *        undefined

 *        undefined

 *        undefined

 *        undefined

 *        undefined

 *        undefined

 *        11

 *

 */

/**

 * (4)for...in循环

 */

// for (const key in numbers) {

//   console.log(numbers[key])

// }

/**

 * 结果为:1

 *        2

 *        3

 *        11

 */

for…in循环用于遍历对象的属性,返回的是属性名。而for…of循环用于遍历可迭代对象(如数组、字符串、Set、Map等),返回的是元素值。


五、

// 4.下面代码的输出是什么?

(() => {
  let x, y
  try {
    throw new Error()
  } catch (x) {
    ;(x = 1), (y = 2)
    console.log(x)
  }
  console.log(x)
  console.log(y)
})()

/**

 * 自执行匿名函数

 */

/**

 * 基础知识:try...catch 语句中的throw语句

 *

 * 当在 JavaScript 中使用 try...catch 语句时,throw 语句用于手动创建一个异常,然后在 catch 块中捕获和处理这个错误。

 *

 *

 * throw 语句的作用:

 * 1.创建异常对象: throw 语句会在执行到它的位置时创建一个异常对象,这个异常对象通常是一个 Error 类型的实例或其子类。

 *

 * 2.中断代码执行: 一旦 throw 语句被执行,代码的正常执行流程会立即中断。这意味着 throw 语句后面的代码不会被执行。

 *

 * 3.(**重点**)此时,在 try 块中,如果异常被抛出,控制流会跳到 catch 块中,这里我们可以访问并处理异常。

 *

 * 4.传递到 catch 块: 抛出的异常会被 JavaScript 运行时引擎捕获,然后尝试匹配适合的 catch 块来处理这个异常。如果在当前作用域内找不到匹配的 catch 块,

 * 异常将被传递到调用堆栈上一层的作用域,以此类推,直到找到合适的 catch 块或者直到异常未被捕获。

 *

 * 总结:throw语句抛出一个异常的实例对象,如果在try中执行到了throw语句,那么try里面的throw语句之后的代码就不会再执行,直接去执行catch里面的代码(看例题就懂了)

 */

// 例题:

// function divide(a, b) {
//   if (b === 0) {
//     throw new Error('Division by zero is not allowed.')
//   }
//   return a / b
// }
// try {
//   const result = divide(10, 0)
//   console.log(111) // 这行不会被执行
//   console.log(result) // 这行不会被执行
// } catch (error) {
//   console.log('An error occurred:', error.message)
// }

// 在这个例子中,throw 语句被用来检查除数是否为零,如果是零,就抛出一个包含错误信息的 Error 对象。

// 然后,在 try 块中,如果异常被抛出,控制流会跳到 catch 块中,这里可以访问并处理异常。

/**

 * 本题解析:

 *

 *  这段代码中的关键是 catch 块内部的 throw 语句。实际上,这里的 throw new Error() 是为了创建一个错误,但是该错误并没有被捕获。让我们来一步步解释:

 *

 *  来解释代码的执行:

 *

 *  第一步:在立即调用的函数表达式内部,声明了两个变量 x 和 y。

 *

 *  第二步:在 try 块中,抛出一个 Error 对象。由于没有匹配的 catch 块来捕获这个错误,所以它会沿着函数调用堆栈向上抛出,直到被全局错误处理机制捕获(如果有的话)。

 *

 *  第三步:在 catch 块内部,有一个变量 x,它是一个参数,而不是外部作用域中的变量。在 catch 块内,将参数 x 的值设置为 1,将 y 的值设置为 2。

 *

 *  第四步:在 catch 块内,执行 console.log(x) 会输出 1,因为它引用的是 catch 块内部的参数 x。

 *

 *  第五步:在 catch 块之外,输出外部作用域中的变量 x。由于 catch 块的参数 x 不影响外部作用域中的变量 x,所以这里的 console.log(x) 输出的是外部作用域中的 x,

 *         但是在外部作用域中,它声明未赋值,因此输出为 undefined。

 *

 *  第六步:由于 y 在 catch 块内被赋值为 2,并且y具有函数作用域,所以在函数内部的任何地方都可以访问并输出 y,输出为 2。

六、

function sayHi() {
  return (() => 0)()
}
console.log(sayHi())
console.log(sayHi)

/**

 * 本题解析:

 *  1.在sayHi函数内部,return后面是一个自执行匿名函数,调用返回值为0,所以return后面值为0,相当于sayHi函数的返回值为0

 *  2.在typeof后面,要注意是sayHi(),而不是sayHi,这表明调用了sayHi函数,而sayHi函数的返回值为0,所以typeof判断的其实是

 *    sayHi函数返回值的类型,typeof sayHi() 就相当于 typeof 0,因此这儿判断类型为number

 */

/**

 * 注意:如果是  let type = typeof sayHi

                console.log(type)

        那么这儿的输出结果就是`function`

 */

const person = { name: 'Lydia' }
function sayHi(age) {
  console.log(`${this.name} is ${age}`)
}
sayHi.call(person, 21)
sayHi.bind(person, 21)

 * call、apply、bind的区别:

 *    相同:(1)都是用来改变函数的this对象的指向的

 *          (2)第一个参数都是this要指向的对象

 *          (3)都可以利用后续参数传参

 *    不同:(1)执行方式不同:call和apply是改变后页面加载之后立即执行,bind是异步代码,改变之后不会立马执行,而是返回一个新的参数

 *          (2)传参方式不同:call和bind是一个一个传入,apply可以使用数组的方式传入

 *          (3)修改this的性质不同:call、apply只是临时修改一次,bind是永久修改this指向

 */

/**

 * this指向问题:

 *    (1)普通函数调用:通过函数名()直接调用,this指向全局对象window

 *    (2)构造函数调用:用new关键字调用时,this指向新new出的对象

 *    (3)对象函数调用:object.method(),this指向这个对象

 *    (4)call、apply、bind:method.call(ctx),this指向第一个参数

 *    (5)箭头函数调用:箭头函数里没有this,所以永远是上层作用域this

 */

/**

 * 在 JavaScript 中,this 在 Node.js 环境和浏览器环境中的行为有一些不同之处。

 *

 *    1.浏览器环境:

 *    在浏览器环境中,this 的行为可能会根据执行上下文的不同情况而变化:

 *

 *    全局上下下文中: 在浏览器环境下的全局作用域中,this 指向 window 对象,即全局对象。

 *

 *    函数上下文中: 在函数内部,this 的值取决于函数是如何被调用的。如果函数是作为普通函数调用,那么 this 将指向全局对象(在非严格模式下),

 *    或者是 undefined(在严格模式下)。如果函数是作为对象的方法调用,this 将指向调用该方法的对象。

 *

 *    2.Node.js 环境:

 *    在 Node.js 环境中,this 的行为也会受到上下文影响,但有一些区别:

 *

 *    全局上下文中: 在 Node.js 的全局上下文中,this 并不指向 window 对象,而是指向一个特殊的对象,称为 global 对象,它类似于浏览器环境中的 window 对象。

 *

 *    模块上下文中: 在 Node.js 中,每个模块都有自己的上下文,而不像浏览器中的全局上下文。在模块内部,this 不会指向 global,而是指向模块本身的导出对象。

 *

 *    需要注意的是,使用箭头函数可以改变 this 的行为,使其与普通函数有所不同。在箭头函数中,this 的值由外围(包含箭头函数的函数或上下文)的 this 值决定,

 *    而不是函数自身的调用方式。

 *

 *    总之,在不同的上下文中,this 的值可能会有所不同,因此在编写代码时需要注意上下文的影响。

 */

/**

 * 本题解析

 */

/**

 *

 * 这段代码涉及了 JavaScript 中的 call 和 bind 方法,以及 this 关键字的用法。让我们逐步解释:

 *

 * 首先,创建了一个名为 person 的对象,其中包含一个 name 属性。

 *

 * 然后,定义了一个名为 sayHi 的函数,它接受一个 age 参数。在函数内部,使用 this.name 来引用调用该函数的对象的 name 属性。

 *

 * 使用 call 方法调用 sayHi 函数,并将 person 对象作为第一个参数传递给 call 方法。第二个参数是传递给 sayHi 函数的参数,即 21。

 * 这样,sayHi.call(person, 21) 会输出 Lydia is 21。这是因为 this 在 sayHi 函数内指向了 person 对象。

 *

 * 使用 bind 方法也是类似的,但它不会立即调用函数,而是创建一个新的函数,将指定的 this 值和参数预设。

 * 然而,在你的代码中,你并没有调用绑定后的函数,因此 sayHi.bind(person, 21) 并没有产生输出。

 *

 * 综合来说,只有 sayHi.call(person, 21) 被调用并产生了输出,而 sayHi.bind(person, 21) 只是返回了一个新的函数,

 * 需要再次调用它才能产生输出。可以这样写:

 *

 * const boundFunction = sayHi.bind(person, 21);

 * boundFunction(); // 输出 "Lydia is 21"

 */

/**

 * Tips:先把前置知识看完,再看本题解析

 */

// 3.下面代码的输出是什么?

var arr = [
  [0, 1],
  [2, 3]
]
var result = arr.reduce(
  (acc, cur) => {
    return acc.concat(cur)
  },
  [1, 2]
)
console.log(result)
// [ 1, 2, 0, 1, 2, 3 ]

/**

 * 解析:

 *  本题为数组扁平化

 * 第一步:acc为传入的[1,2],当前项为[0,1],执行acc.concat(cur),则返回值为[ 1 , 2 , 0 , 1 ]

 * 第二部:acc为上次返回值[ 1 , 2 , 0 , 1 ],再和第二项[ 2 , 3 ]合并,最终为[ 1, 2, 0, 1, 2, 3 ]

 */

/*----------------------------前置基础知识----------------------------*/

/**

 * 一、reduce()函数

 * 定义

 * 1.reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

 *

 * 2.reduce() 可以作为一个高阶函数,用于函数的 compose

 *

 * 3.reduce() 对于空数组是不会执行回调函数的

 *

 * 4.reduce() 会遍历数组的每一项

 *

 */

/**

 * 语法

 * array.reduce(function(prev, currentValue, currentIndex, arr), initialValue)

 *

 * 说明:

 * reduce函数,接收两个参数,一个为回调函数,一个为初始赋值,最常用的场景就是,计算数组中的每一项的总和。

 *

 * 第一个参数的回调函数又接收四个参数,分别为(初始值或计算结束后的返回值,当前元素,当前元素的索引,当前元素所属的数组对象)

 *

 * 第二个参数是传给函数的初始值,非必传

 *

 * prev:函数传进来的初始值或上一次回调的返回值

 * currentValue:数组中当前处理的元素值

 * currentIndex:当前元素索引

 * arr:当前元素所属的数组本身

 * initialValue:传给函数的初始值

 */

// 例题1

/**
var arr = [1, 5, 8, 6, 4]
var sum = arr.reduce((prev, cur, index, arr) => {
  console.log(prev, cur, index)
  return prev + cur
}, 2)
console.log(sum)
*/

/**

 * 例题1解析:

 *  首先明确一点:reduce() 方法接收一个函数作为累加器,理解累加器的意思

 *  第一步:prev是传进来的值`initialValue`为2,注意:这儿如果没有传值,则为第一个值1,所以prev, cur, index分别是2,1,0

 *  第二步:prev:函数上一次回调的返回值,也就是return 后面prev+cur的值,就是2+1,即prev为3,prev, cur, index分别是3,5,1

 *  第三步:prev:prev+cur的值,就是3+5,即prev为8,prev, cur, index分别是8,8,2

 *  第四步:prev:16,prev, cur, index分别是16,6,3

 *  第五步:prev:22,prev, cur, index分别是22,4,4

 *  最终:sum的值为reduce函数返回值,即最后一次执行的return值,也就是26

 */

// 总结:如果没有提供initialValue,reduce 会从索引1的地方开始执行回调方法,跳过第一个索引。如果提供initialValue,从索引0开始。

/**

 * prev   cur   index

 * 2       1      0

 * 3       5      1

 * 8       8      2

 * 16      6      3

 * 22      4      4

 */

/**

 * 二、concat()方法

 *

 * concat()方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。字符串同理

 *

 * 语法:Array.concat(val1[,val2[,val3...[,valN]]]) 或者 arr1.concat(arr2)

 *

 * 参数:valN:可以填数组或值,把他们合并到一个新数组里,原数组不变,如果不写,则默认对Array进行一个浅复制

 */

九、

// 2.结果是什么?

let result = [...'Lydia']
console.log(result)

/**

 * 扩展运算符:对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中

 */

/**

 * 在ES6中。 三个点(...) 有2个含义。分别表示 扩展运算符 和 剩余运算符。

 */

/******************扩展运算符(spread)********************/

// 扩展运算符的主要作用是将一个数组转为用逗号分隔的参数序列,它好比 rest 的逆运算

// 最后总结:

/**

 * 扩展运算符用三个点号表示,功能是把数组或类数组对象展开成一系列用逗号隔开的值

 * rest运算符也是三个点号,不过其功能与扩展运算符恰好相反,把逗号隔开的值序列组合成一个数组

 *

 * 当三个点(...)在等号左边,或者放在形参上。为 rest 运算符

 * 当三个在等号右边,或者放在实参上,是 spread运算符

 *

 * 或者说:放在被赋值一方是rest 运算符。放在赋值一方是 spread运算符。

 */

十、

var a = 0,
  b = 0
function A(a) {
  A = function (b) {
    console.log(a + b++)
  }
  console.log(a++) // 第一次执行到这的时候,变量a在内存中的值已经为2
}
A(1)
A(2)

// 1.首先,全局变量'a'和'b'被初始化为0

// 2.调用函数A(1),进入函数体内:

/**

 * (1)传入参数 a 为1,

 * (2)函数A被重新赋值为一个新的函数

 * (3)在函数内部,打印输出a++,此时打印出的a为1,然后进行a++,在内存中a的值为2

 */

// 3.调用函数A(2),进入函数体内:

/**

 * (1)A函数已被定义,此时执行的是新定义的函数 function(b)

 * (2)传入b的值为2

 * (3)在新定义的函数内部,计算并输出a+b++,其中a为之前的参数2,b是2(传入的参数)

 * (4)打印出a+b++的结果为4,b再执行自增,最终b的值变成3

 */

/**

 * (1)变量作用域和提升: JavaScript 中的变量有不同的作用域,包括函数作用域和全局作用域。

 *      在函数内部重新定义变量会覆盖外部的同名变量。此外,变量和函数在执行之前会被提升到作用域的顶部。

 *

 * (2)函数声明和函数表达式: JavaScript 中可以使用函数声明和函数表达式来创建函数。函数声明会被提升,

 *    而函数表达式则不会。在这个题目中,函数 A 被反复赋值,所以会导致不同的行为。

 *

 * (3)闭包: 在 JavaScript 中,函数可以访问其外部作用域中的变量。在这个题目中,函数 A 的内部函数(也叫闭包)

 *    会保留对外部变量 a 的引用,即使 A 函数被重新赋值,闭包中的引用仍然存在。

 *

 * (4)参数传递和自增运算: 函数可以接受参数,并在内部进行操作。自增运算符 ++ 会增加变量的值。

 *

 * (5)函数调用和执行顺序: 函数按照调用的顺序依次执行,但由于 JavaScript 的事件循环机制,异步操作可能会导致某些代码在其他代码之前执行。

 */


评论(

您还未登录,请先去登录
表情
查看更多

相关作者

  • 获取点赞0
  • 文章阅读量264

相关文章

联系小鹿线

咨询老师

咨询老师

扫码下载APP