JavaScript学习重点难点知识总结(长期更)

376 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1、 防抖和节流

  • 防抖 触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。比如登录按钮避免用户点击太快,以致于发送了多次请求,需要防抖,只在最后一次点击后发送;掘金草稿之类的,在文本编辑器中实时保存,当无任何更改操作一秒后进行保存。
        <div class="container">
            <input type="text" id="searchText">
        </div>
        let timer = null
        document.getElementById('searchText').addEventListener('input', function () {
            if (timer) clearTimeout(timer)
            timer = setTimeout(() => {
                console.log(`发送查询请求$(this.value)`);
            }, 1000)
        })
  • 节流 高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。控制事件发生的频率,如控制为 1s 发生一次,甚至 1 分钟发生一次。比如滑动 scroll 事件,每隔一秒计算一次位置信息等。
        <div class="container">
            <input type="text" id="searchText">
        </div>
        let lastTime = null
        document.getElementById('searchText').addEventListener('input', function () {
            if (lastTime == null) {
                console.log(`发送查询请求$(this.value)`);
                lastTime = new Date().getTime()
            } else {
                let now = new Date().getTime()
                if (now - lastTime > 2000) {
                    console.log(`发送查询请求$(this.value)`);
                    lastTime = new Date().getTime()
                }
            }
        })
  • 总结 防抖:防止抖动,单位时间内事件触发会被重置,避免事件被误伤触发多次。代码实现重在清零 clearTimeout。防抖可以比作等电梯,只要有一个人进来,就需要再等一会儿。业务场景有避免登录按钮多次点击的重复提交。节流:控制流量,单位时间内事件只能触发一次,与服务器端的限流 (Rate Limit) 类似。代码实现重在开锁关锁 timer=timeout; timer=null。节流可以比作过红绿灯,每等一个红灯时间就可以过一批。 两者理解区别重点在于防抖是触发n秒后才执行,而节流是触发中每隔n秒只执行一次,一个是触发中,一个是触发后。

2、 理解拷贝之浅拷贝、深拷贝,怎么实现?

JS中的数据类型分为:
基本类型:string, number, boolean, null, undefined、symbol、bigint;
引用类型:Object,特殊的有Array, Function, Date, Math等。
基本数据类型: 名值存储在栈内存中;
引用数据类型: 名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值。

  • 浅拷贝:简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝。
  • 深拷贝:当修改A时,如果B不跟着变,说明这是深拷贝。 浅拷贝方法
  • Object.assign(),拷贝的是对象的属性的引用,而不是对象本身。
    var obj = { a: {a: "hello", b: 21} };
    var initalObj = Object.assign({}, obj);
    initalObj.a.a = "changed";
    console.log(obj.a.a); //  "changed"

当object只有一层的时候,是深拷贝

    var obj1 = { a: 10, b: 20, c: 30 };
    var obj2 = Object.assign({}, obj1);
    obj2.b = 100;
    console.log(obj1);
    // { a: 10, b: 20, c: 30 } <-- 沒被改到
    console.log(obj2);
    // { a: 10, b: 100, c: 30 }
  • 直接用=赋值
    let a=[0,1,2,3,4],
    b=a;
    console.log(a===b);              //true
    a[0]=1;
    console.log(b);                  //[1,1,2,3,4]
  • 扩展运算符... 是一个 es6 / es2015特性,提供了一种非常方便的方式来执行浅拷贝,与 Object.assign ()的功能相同。(一层深拷贝,多层浅拷贝)。
    let obj1 = { name: 'Kobe', address:{x:100,y:100}}
    let obj2= {... obj1}
    obj1.address.x = 200;
    obj1.name = 'wade'
    console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
  • Array.prototype.concat()
    let arr = [1, 3, {
        username: 'kobe'
        }];
    let arr2 = arr.concat();    
    arr2[2].username = 'wade';
    console.log(arr); //[ 1, 3, { username: 'wade' } ]
  • Array.prototype.slice()
    let arr = [1, 3, {
        username: ' kobe'
        }];
    let arr3 = arr.slice();
    arr3[2].username = 'wade'
    console.log(arr); // [ 1, 3, { username: 'wade' } ]

深拷贝方法

  • 对象只有一层的话可以使用上面的:Object.assign()函数
  • 转成 JSON 再转回来
    var obj1 = { body: { a: 10 } };
    var obj2 = JSON.parse(JSON.stringify(obj1));
    obj2.body.a = 20;
    console.log(obj1);
    // { body: { a: 10 } } <-- 沒被改到
    console.log(obj2);
    // { body: { a: 20 } }
    console.log(obj1 === obj2);
    // false
    console.log(obj1.body === obj2.body);
    // false
  • 递归拷贝
    function deepClone(initalObj, finalObj) {    
      var obj = finalObj || {};    
      for (var i in initalObj) {        
        var prop = initalObj[i];        // 避免相互引用对象导致死循环
        if(prop === obj) {            
          continue;
        }        
        if (typeof prop === 'object') {
          obj[i] = (prop.constructor === Array) ? [] : {};            
          arguments.callee(prop, obj[i]);
        } else {
          obj[i] = prop;
        }
      }    
      return obj;
    }
    var str = {};
    var obj = { a: {a: "hello", b: 21} };
    deepClone(obj, str);
    console.log(str.a);
  • jquery jquery 提供$.extend可以用来做 Deep Copy。
    var $ = require('jquery');
    var obj1 = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    var obj2 = $.extend(true, {}, obj1);
    console.log(obj1.b.f === obj2.b.f);
    // false

3、 JS数据类型转换

js数据类型转换有两种 1、强制类型转换; 2、隐式转换

(1)强制转换

  • 其他的数据类型转换为String

    方式一:toString()方法
    注意:null和undefined这两个值没有toString,如果调用他们的方法,会报错

        var a = 123
        a.toString()//"123"
        var b = null;
        b.toString()//"报错"
        var c = undefined
        c.toString()//"报错"
    
    Number 类型的 toString() 方法的基模式,可以用不同的基输出数字,例如二进制的基是 2,八进制的基是 8,十六进制的基是 16
    
        var iNum = 10;
        alert(iNum.toString(2));        //输出 "1010"
        alert(iNum.toString(8));        //输出 "12"
        alert(iNum.toString(16));       //输出 "A"
    

    方式二:String()函数
    使用String()函数做强制类型转换时,对于Number和Boolean实际上就是调用的toString()方法,
    但是对于null和undefined,就不会调用toString()方法,它会将null直接转换为"null",将undefined 直接转换为"undefined"

      var a = null
      String(a)//"null"
      var b = undefined
      String(b)//"undefined"
    

    String方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。

    String({a: 1}) // "[object Object]"
    String([1, 2, 3]) // "1,2,3"
    
  • 其他的数据类型转换为Number
    方式一:使用Number()函数
    分成两种情况讨论,一种是参数是原始类型的值,另一种是参数是对象
    (1)原始类型值

    ①字符串转数字

    如果是纯数字的字符串,则直接将其转换为数字

    如果字符串中有非数字的内容,则转换为NaN

    如果字符串是一个空串或者是一个全是空格的字符串,则转换为0

    Number('324') // 324
    Number('324abc') // NaN
    Number('') // 0
    

    ②布尔值转数字:true转成1,false转成0

    Number(true) // 1
    Number(false) // 0
    

    ③undefined转数字:转成NaN

    Number(undefined) // NaN
    

    ④null转数字:转成0

    Number(null) // 0
    

    ⑤Number() 接受数值作为参数,此时它既能识别负的十六进制,也能识别0开头的八进制,返回值永远是十进制值

    Number(3.15);    //3.15
    Number(023);     //19
    Number(0x12);    //18
    Number(-0x12);   //-18
    

    (2)对象

    简单的规则是,Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组。

    Number({a: 1}) // NaN
    Number([1, 2, 3]) // NaN
    Number([5]) // 5
    

    方式二:parseInt() & parseFloat()

    这种方式专门用来对付字符串,parseInt()一个字符串转换为一个整数,可以将一个字符串中的有效的整数内容取出来,然后转换为Number。parseFloat()把一个字符串转换为一个浮点数。parseFloat()作用和parseInt()类似,不同的是它可以获得有效的小数。

    console.log(parseInt('.21'));        //NaN
    console.log(parseInt("10.3"));        //10
    console.log(parseFloat('.21'));      //0.21
    console.log(parseFloat('.d1'));       //NaN
    console.log(parseFloat("10.11.33"));  //10.11
    console.log(parseFloat("4.3years"));  //4.3
    console.log(parseFloat("He40.3"));    //NaN
    

    parseInt()在没有第二个参数时默认以十进制转换数值,有第二个参数时,以第二个参数为基数转换数值,如果基数有误返回NaN

    console.log(parseInt("13"));          //13
    console.log(parseInt("11",2));        //3
    console.log(parseInt("17",8));        //15
    console.log(parseInt("1f",16));       //31
    

    有个题目可以参考一下

    问题:["1", "2", "3"].map(parseInt) 答案是多少?
    答案:[1, NaN, NaN]
    为什么呢?
    map函数将数组的每个元素传递给指定的函数处理,并返回处理后的数组,所以['1','2','3'].map(parseInt)
    就是将字符串123作为元素;012作为下标分别调用 parseInt 函数。即分别求出 parseInt('1',0), 
    parseInt('2',1), parseInt('3',2)的结果。
    parseInt('1',0)=1parseInt('2',1)=NaN, 
    parseInt('3',2)=NaN.
    

    两者的区别:Number函数将字符串转为数值,要比parseInt函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN。

    parseInt('42ca') // 42
    Number('42ca') // NaN
    

    上面代码中,parseInt逐个解析字符,而Number函数整体转换字符串的类型。
    另外,对空字符串的处理也不一样

    Number("   ");     //0    
    parseInt("   ");   //NaN
    
  • 其他的数据类型转换为Boolean

    Boolean(undefined) // false
    Boolean(null) // false
    Boolean(0) // false
    Boolean(NaN) // false
    Boolean('') // false
    Boolean({}) // true
    Boolean([]) // true
    Boolean(new Boolean(false)) // true
    

(2)隐式转换

  • 自动转换为布尔值
    JavaScript 遇到预期为布尔值的地方(比如if语句的条件部分),就会将非布尔值的参数自动转换为布尔值。系统内部会自动调用Boolean函数。
  • 自动转换为数值
    对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。
  • 自动转换为字符串
    如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法。

4 、继承