JS常见问题

34 阅读6分钟

1、浅拷贝与深拷贝

1.1 浅拷贝

拷贝对象基本类型的值与拷贝对象引用类型的内存地址。

Object.assign()

let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' }; 
let obj2 = Object.assign({}, obj1);

展开运算符...

let obj1 = { name: 'Kobe', address:{x:100,y:100}};
let obj2= {... obj1};

Array.prototype.concat()

let arr = [1, 3, { username: 'kobe' }]; 
let arr2 = arr.concat();

Array.prototype.slice()

let arr = [1, 3, { username: ' kobe' }]; 
let arr3 = arr.slice();

1.2 深拷贝

拷贝对象基本类型的值与拷贝对象引用类型,新的对象处理引用类型的时候不会影响到被拷贝的对象。

JSON.parse(JSON.stringify())

let arr = [1, 3, { username: ' kobe' }]; 
let arr4 = JSON.parse(JSON.stringify(arr));

缺点是不能拷贝函数与正则表达式。

手写递归实现

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 如果对象里面引用了自己,则使用hash,避免循环递归。
  if (hash.get(obj)) return hash.get(obj);
  // 是对象的话就要进行深拷贝
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);

1.3 参考资料

juejin.cn/post/684490…

2、原型链

image.png

当调用对象的属性后,如果当前对象没有,则会通过原型链去查找该属性,直到没有找到为止,这个过程是自动的。

常用的原型链查找对象属性的流程

image.png

3、箭头函数与普通函数的区别

(1)箭头函数没有自己的this对象。

(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

4、New操作符做了什么事情?

在 JavaScript 中,new 操作符用于创建一个对象实例。它执行以下步骤:

  1. 创建一个空对象。
  2. 将新对象的原型链接到构造函数的原型对象上。
  3. 将构造函数的作用域赋给新对象(因此 this 关键字指向新对象)。
  4. 执行构造函数,并将参数传递给它。
  5. 如果构造函数返回一个对象,则返回该对象;否则,返回新创建的对象。

以下是一个示例,演示了 new 操作符的使用:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const john = new Person('John', 25);
console.log(john.name); // 输出: John
console.log(john.age); // 输出: 25

在上面的示例中,new Person('John', 25) 创建了一个新的 Person 对象实例,并将其赋值给 john 变量。

5、js编程

5.1 异步红绿灯

要求

红灯3s亮一次,绿灯1S亮一次,黄灯2S亮一次,如何让3个灯不断交替重复下去?

思路:

1、同步任务编写红绿灯代码
2、异步任务编写时间流程管理(关键)
3、同步任务递归调用异步任务(关键)

方法1、使用回调函数完成

// 编写同步任务
function red(){
    console.log('red');
}
function green(){
    console.log('green');
}
function yellow(){
    console.log('yellow');
}


// 编写异步任务

function task(time,color,callback) {
    setTimeout(function(){
        if(color == 'red') {
            red();
        }
        else if(color == 'green') {
            green();
        }
        else if(color == 'yellow') {
            yellow();
        }
        callback();
    },time)
}
// 执行异步任务

function repetition(){
    task(3000,'red',()=>{
        task(2000,'green',()=>{
            task(1000,'yellow',repetition)
        })
    })
}
repetition()

方法2、使用promise完成

// 编写同步任务

function red(){
    console.log('red');
}
function green(){
    console.log('green');
}
function yellow(){
    console.log('yellow');
}

// 编写异步任务
function task(time,color) {
    return new Promise((resolve,reject)=>{

        setTimeout(function(){

            if(color == 'red') {

                red();

            }

            else if(color == 'green') {

                green();

            }

            else if(color == 'yellow') {

                yellow();

            }

            resolve();

        },time)
    })
}

// 执行异步任务

function repetition(){
    task(3000,'red')
    .then(()=>{
        return task(2000,'green');
    })
    .then(()=>{
        return task(1000,'yellow');
    })
    .then(repetition)

}

repetition()

方法3、使用async


// 编写同步任务

function red(){
    console.log('red');
}

function green(){
    console.log('green');
}

function yellow(){
    console.log('yellow');
}

// 编写异步任务

function task(time,color) {

    return new Promise((resolve,reject)=>{

        setTimeout(function(){

            if(color == 'red') {

                red();

            }

            else if(color == 'green') {

                green();

            }

            else if(color == 'yellow') {

                yellow();

            }

            resolve();

        },time)

    })
}
// 执行异步任务

async function repetition(){

    await task(3000,'red');

    await task(2000,'green');

    await task(1000,'yellow');

    repetition()

}
repetition()

5.2 判断数组的最深深度

const getArrayDepth = (array) => {
    if (Array.isArray(array)) {
        return 1 + Math.max(...array.map(ele => getArrayDepth(ele)))
    } else {
        return 0
    }
}

5.3 根据元素的parentId与id,将其合并为树数组

6、this指向

this的指向问题

7、变量提升与作用域

参考这个

额外的

let array = []
for(var i = 0; i < 3; i++) {
    array[i] = function() {
        console.log(i)
    }
}
array[0]() // 3
array[1]() // 3
array[2]() // 3

8、闭包

闭包是指在一个函数内部定义的函数可以访问外部函数的变量,即使外部函数执行结束后,内部函数仍然可以访问外部函数的变量。

作用

  1. 封装,将变量或者函数封装在一个作用域内,防止其被外部访问和修改。
  2. 保存状态,内部函数可以访问外部函数的变量,因此可以通过闭包来保存变量的状态,如保存计时器。
  3. 模块化,避免了全局命名空间的污染

9、事件循环

参考这个

10、Array的方法

developer.mozilla.org/zh-CN/docs/…

11、var与let、const

作用域

  1. var声明的变量,其作用域为整个函数,
  2. let、const声明的变量,其作用域为整个块级。

变量提升

  1. var声明的变量会被提升,也就是说,在函数中你使用这个变量可以在它的声明之前,它会被提升,值为undefind,不会报错。
  2. let、const不会被提升,使用该变量必须在声明之后。

var的一些业务常见问题

  1. 在纯净的script标签,var声明的变量会变成全局变量。
<!DOCTYPE html>
<html>
<head>
    <title>Test let</title>
</head>
<body>
    <script>
        let globalLet = "I am a global let variable";
    </script>
    <script>
        console.log(window.globalLet); // 输出: undefined
        console.log(globalLet); // 输出: I am a global let variable
    </script>
</body>
</html>
  1. 在将script标签设置为type="module"后,var声明的变量作用域只能存在于此标签中。
<!DOCTYPE html>
<html>
<head>
    <title>Test module</title>
</head>
<body>
    <script type="module">
        var moduleVar = "I am a module var variable";
        let moduleLet = "I am a module let variable";
        const moduleConst = "I am a module const variable";
        console.log(moduleVar); // 输出: I am a module var variable
        console.log(moduleLet); // 输出: I am a module let variable
        console.log(moduleConst); // 输出: I am a module const variable
    </script>
    <script>
        console.log(window.moduleVar); // 输出: undefined
        console.log(window.moduleLet); // 输出: undefined
        console.log(window.moduleConst); // 输出: undefined
    </script>
</body>
</html>
  1. 使用构建工具构建的js文件,当js文件用var声明了一个变量,那么它的作用域还是仅存在于此文件,它在构建构建里面会被打包成一个模块。
var myVar = "I am a var variable in a module";
console.log(myVar); // 输出: I am a var variable in a module

构建完成后

(function(modules) {
  // Webpack引导代码...
  var installedModules = {};

  function __webpack_require__(moduleId) {
    // 模块加载逻辑...
  }

  return __webpack_require__(__webpack_require__.s = "./src/index.js");
})({
  "./src/index.js": (function(module, exports) {
    var myVar = "I am a var variable in a module";
    console.log(myVar); // 输出: I am a var variable in a module
  })
});