js常见问题

1,174 阅读4分钟

js常见问题

1.变量提升和函数提升,函数表达式和函数声明的区别

  • 会把var声明的变量提前到当前作用域声明,只提前声明,不提前赋值

  • 会提前声明函数,只提前声明,不提前调用

  • 当变量和函数同名时,先提升同名的变量,再提升同名的函数,函数的权重高(函数会覆盖变量)

题目一:
var a =1;
console.log(a);
a();
function a(){
 console.log('aaa')
}
console.log(a)

预解析
//a 变量
//a 函数

执行
//a = 1
// a()

结果
// 1
//报错 a is not a function


题目二
console.log(a)
a();
var a =1;
function a(){
 console.log('aaa')
}
console.log(a);

预解析
//a 变量
//a 函数

执行
//log a ->是一个函数
// a() -> 'aaa'
//log a ->1

结果
// 函数
//'aaa'
//1

注意上面2个题目的区别,一个报错,一个不报错,主要在于函数的执行阶段。题目一是先把a赋值再调用;题目二是先调用函数再给a赋值

  • 函数声明会提升,函数表达式不会提升(函数表达式会提升等号左边的变量为undefined,不会提升函数)
 <!-- 题目一:函数表达式 -->
     // console.log(fn); //undefined

     // fn() //报错fn is not defined

     // var fn = function() {
     //     console.log('fn')

     // }



<!-- 题目二:函数声明 -->

     console.log(fn); //函数

     fn() //'fn'
     function fn() {
         console.log('fn')
     }

再看看下面这个题吧:

 //  词法作用域的一个坑,可以画图理解
 var a = {
     x: 1
 }
 var b = a;
 a.x = a = {
     n: 1
 };
 // 运算符的优先级问题:
 // 算术运算符>关系运算符>逻辑运算符>赋值运算符
 console.log(a.x); //undefined 点运算符大于赋值运算符
 console.log(b.x); // {n : 1}

浅拷贝和深拷贝

浅拷贝

对象的浅拷贝
const obj = {
            name: "bwf",
            age: 18
        }
// 法一 Object.assign
const obj1 = Object.assign({}, obj);
obj1.name = 'wmy'
console.log(obj)

// 法二 扩展运算符
const obj2 = {
...obj1
}
obj2.name = 'wmy'
console.log(obj)
数组的浅拷贝
// 法一:扩展运算符
 const arr = [1, 2, 3]
 const arr2 = [...arr]
 arr2[0] = 9
console.log(arr, arr2)

// 法二 slice
const arr = [1, 2, 3]
const arr1 = arr.slice();
arr1[0] = 9
console.log(arr, arr1)

// 法三 concat
const arr = [1, 2, 3]
const arr3 = arr.concat()
arr3[0] = 9
console.log(arr, arr3)

深拷贝

   const person = {
           name: 'bwf',
           age: 18,
           job: {
               name: 'fe',
               salary: 14
           },
           sayHello() {
               console.log(`大家好,我叫${this.name}`)
           }
       }
       // Object.assign只实现了对象第一层的拷贝
       const p1 = Object.assign({}, person);
       p1.name = 'wmy'
       p1.job.salary = 20
       console.log(person)

   // 法一:递归拷贝
   function deepClone(obj) {
       // 定义一个新的对象或数组,遍历源对象或数组,复制到新对像或数组中
       const target = Array.isArray(obj) ? [] : {};
       if (obj && typeof obj === "object") {
           for (const key in obj) {
               // 如果对象的属性又是一个对象,则递归复制
               if (obj[key] && typeof obj[key] === "object") {
                   target[key] = deepClone(obj[key])
               } else {
                   target[key] = obj[key]
               }
           }

       }
       return target;
   }
   const p2 = deepClone(person)
   p2.name = 'wmy'
   p2.job.salary = 20
   console.log(person)


   // 法二:JSON.stringify和JSON.parse
   // 缺点: 无法实现对对象中方法的深拷贝,会显示为undefined
   function deepClone2(obj) {
       var _obj = JSON.stringify(obj)
       var objClone = JSON.parse(_obj)
       return objClone;

   }
   const p3 = deepClone2(person)
   p3.name = 'wmy'
   p3.job.salary = 20
   p3.sayHello = function() {
       console.log('hello')
   }
   console.log(person)
   person.sayHello()

js事件循环机制

juejin.cn/post/707304…

闭包

  • 概念:定义在函数内部的函数,该函数中使用了外部函数的变量

  • 作用:实现数据的私有化,因为全局变量会被污染

  • 缺点:内存溢出,怎么溢出的?(面试的时候问过) 因为闭包函数中引用的变量一直在内存中,没有被垃圾回收机制回收掉。

  • 那js的内存管理和垃圾回收机制又是咋样的呢?

    在 JS 中创建一个变量的时候,系统会根据,变量的类型,自动为其分配对应的内存(基础类型 -> 栈内存,固定大小;对象类型 -> 堆内存,根据需要分配大小)。
    正常情况下,当这些变量不再被使用的时候,就会被回收,内存被释放。

    JavaScript 中全局变量的在浏览器卸载页面才会被销毁;函数中用var,let,const定义的变量,一般是在函数调用结束后就会被销毁

    现在各大浏览器通常采用的垃圾回收有两种方法:标记清除、引用计数。一般都是标记清除

  • 应用场景?思考:现在有了块级作用域,应该不需要了吧?(fix)

下面是一些闭包函数的例子: 参见 闭包

1.什么是闭包

     function lazy_sum(arr) {
            var sum = function() {
                return arr.reduce(function(x, y) {
                    return x + y;
                });
            }
            return sum;
        }
        // 注意到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用 fix 这句话怎么理解

2.理解var定义的变量和立即执行函数


     function count() {
            var arr = [];
            for (var i = 1; i <= 3; i++) {
                arr.push(function() {
                    return i * i;
                });
            }
            return arr;
        }

        var results = count();
        var f1 = results[0];
        // var f2 = results[1];
        // var f3 = results[2];
        console.log('f1', f1()); //16
        console.log('f2', f2()); //16
        console.log('f3', f3()); //16
        
       //打印结果 全部都是`16`!原因就在于返回的函数引用了变量`i`,但它并非立刻执行。
       //等到3个函数都返回时,它们所引用的变量`i`已经变成了`4`,因此最终结果为`16`。
       
       那如果想得到1,4,9要怎么写呢
       

    function count() {
      var arr = [];
      for (var i = 1; i <= 3; i++) {
        arr.push((function () {
          return i * i;
        })(i));
      }
      return arr;
    }

    var results = count();
    var f1 = results[0];
    var f2 = results[1];
    var f3 = results[2];
    console.log('f1', f1);
    console.log('f2', f2);
    console.log('f3', f3);

3.闭包可以防止全局变量的污染


        function fn() {
            var a = 1;
            return function() {
                return a
            }
        }
        var f1 = fn() //返回函数,每一次调用都得到一个最初始的值
        var a1 = f1(); //1
        a1++;
        a1++;

        var a2 = f1(); //1
        a2++;
        a2++;
        console.log(a1, a2)

4.实现第一秒打印1,第二秒打印2,。。。

   for (var i = 1; i < 10; i++) {
            setTimeout(function() {
                //打印10次10 
                console.log(i);
            }, 1000 * i);
   }


        // 1
        for (let i = 1; i < 10; i++) {
            setTimeout(function() {
                console.log(i);
            }, 1000 * i);
        }


        // 2
        for (var i = 1; i < 10; i++) {
            (function(i) {
                setTimeout(function() {
                    console.log(i);
                }, 1000 * i);

            })(i)
        }


        // 3
        for (var i = 1; i < 10; i++) {
            var fn = (function(num) {
                return function() {
                    console.log(num);
                }
            })(i);
            setTimeout(fn, 1000 * i);
        }



        // 4
        // setTimeout( fn, 毫秒数, 需要传递给fn的参数 )
        // fix 这个是一次性打印出来的呀?
        for (var i = 1; i < 10; i++) {
            setTimeout(function(i) {
                console.log(i);
            }, 1000, i);
        }


数组和字符串常用方法

节流和防抖的实现

vue3js.cn/interview/J…

 // debounce 防抖:n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
    function debounce(fn, wait) {
        let timer;
        return function() {
            if (timer) {
                clearTimeout(timer)
            }
            timer = setTimeout(fn, wait)
        }

    }
 //throttle 节流:n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
// throttle 节流:时间戳实现
const throttle1 = function(func, delay) {
        var prev = Date.now();
        return function() {
            var context = this;
            var args = arguments;
            var now = Date.now();
            if (now - prev >= delay) {
                func.apply(context, args);
                prev = Date.now();
            }
        }
    }
     // throttle 节流:定时器实现
    const throttle2 = function(func, delay) {
        var timer = null;
        return function() {
            var context = this;
            var args = arguments;
            if (!timer) {
                timer = setTimeout(function() {
                    func.apply(context, args);
                    timer = null;
                }, delay);
            }
        }
    }
    function handle() {
            console.log('handle')
        }

    const scrollBox = document.getElementById('scroll-box')
    scrollBox.addEventListener('scroll', throttle2(handle, 1000))

call,apply,bind的区别和共同点

  • 改变函数执行时的上下文,call,apply的第二个参数不同,call传参数列表,apply传数组,bind返回的是一个函数,调用后才会执行
  • call,apply,bind的应用场景:
// 2.1求数组中的最大值
const arr = [1, 9, 3, 4]
const res1 = Math.max(...arr)
const res2 = Math.max.apply(null, arr)
const res3 = Math.max.call(null, ...arr)

// 2.2判断变量类型 
const obj = {
    name: 'bwf'
}
console.log(typeof obj) //object
console.log(typeof arr) //object
console.log(typeof null) //object
console.log(Object.prototype.toString.call(obj)) //[object Object]
console.log(Object.prototype.toString.call(arr)) //[object Array]
console.log(Object.prototype.toString.call(null)) //[object Null]
// 2.3继承

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

function Student(name, age) {
    Person.call(this, name, age)

}
const s = new Student('bwf', 18)