JavaScript高级笔记

目录

作用域

局部作用域

函数作用域

块级作用域

全局作用域

作用域链

闭包

变量提升

函数

函数提升

函数参数Plus版

动态参数 --- arguments

剩余参数 --- ...(三个点)

补充:展开运算符(也是三个点 ... 在数组中)

箭头函数

箭头函数的this

解构赋值

数组解构

数组解构变量和单元值数量

对象解构

多级对象解构

forEach遍历数组

filter筛选数组

构造函数 

构造函数的写法

实例成员和静态成员

内置构造函数

Object静态方法

扩展提示 数组方法

数组 reduce方法

数组find方法

数组 every和some方法

String常见方法

Number常见方法

面向对象

面向对象和面向过程

原型 prototype

构造函数和原型对象里的this指向

原型对象里的constructor属性

对象原型__proto__

构造函数、原型对象、实例对象三者的关系

原型链

浅拷贝和深拷贝

浅拷贝

递归函数

深拷贝

利用递归函数实现深拷贝

利用lodash实现深拷贝

利用JSON实现深拷贝

this相关知识

普通函数的this指向

箭头函数中的this指向

call方法改变this指向

apply方法改变this指向

bind方法改变this指向

总结:call和apply以及bind三者的区别

节流和防抖


作用域

作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。

局部作用域

局部作用域分为函数作用域块作用域

函数作用域

定义:在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。

    <script>
        function fn(){
            // 函数内部声明的变量 num
            let num = 10;
            console.log(num); // 10
        }
        fn()
        // 访问函数内部变量 num
        console.log(num); // 报错 num未定义
    </script>

总结:

        1.函数内部声明的变量,函数外部无法访问。

        2.函数的形参也是函数内部的局部变量。

        3.不同函数内部声明的变量无法互相访问。

        4.函数执行完毕后,函数内部的变量实际被清空了。

块级作用域

定义:在JS中使用{}包裹的代码称为代码块,代码块内部声明的变量外部无法访问(也有可能访问)

        {
            // a 只能在代码块中被访问
            let a = 10;
            console.log(a); // 10
        }
        console.log(a); // 超出作用域 报错

        if(true){
            let b = 20;
            console.log(b); // 20
        }
        console.log(b); //报错 未定义

注意:JS中除了变量还有常量,常量和变量的本质区别就是,常量必须有初始值且不允许被重新赋值,但常量值为对象时它的属性和方法可以被重新赋值(因为对象的地址存放在栈中,数据存放在堆中,更改对象的属性是更改的堆中的数据,而不是地址,所以不会报错。但是不能给对象重新赋值)

        const num = 10;
        num = 20;
        console.log(num); // 报错

        const obj = {
            uname:'张三',
            age:20,
        }
        obj.uname = '李四';
        obj.age = 20;
        console.log(obj.uname,obj.age); // 李四 20

        const obj = {} // 不能给对象重新赋值

总结:

        1.let声明的变量会产生块级作用域,var不会产生

        2.const声明的变量也会产生块级作用域

        3.不同代码块之间的变量无法互相访问

        4.推荐使用let

全局作用域

定义:全局作用域中声明的变量,任何其他作用域都可以被访问。

<script>标签和 .js 文件的最外层就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。

总结:

        1.为window对象动态添加的属性默认也是全局的,不推荐!

        2.函数中未使用任何关键字声明的变量为全局变量,不推荐!

        3.尽可能减少的声明全局变量,放在全局变量污染。

作用域链

定义:函数内部允许创建新的函数,会产生新的作用域,这时作用域产生了嵌套关系。类型父子关系的作用域链关联在一起形成了链状的解构

        // 全局作用域
        let a = 1;
        let b = 2;

        // 局部作用域
        function fn1(){ 
            let c = 3;
            // 局部作用域
            function fn2(){
                let d = 4
            }
        }

作用域链本质上是底层的变量查找机制,当函数执行时,会根据就近原则查找当前函数作用域中查找变量,如果没有会依次向上级作用域查找变量直到全局作用域。

    <script>
        // 全局作用域
        let a = 1;
        let b = 2;

        // 局部作用域
        function fn1(){
            let a = 10;
            console.log(a); // 10
            // 局部作用域
            function fn2(){
                console.log(b); // 2
            }
            fn2()
        }
        fn1();
    </script>

总结:

        1.嵌套关系的作用域串联起来形成了作用域链。

        2.相同作用域链中按着从小到大的规则查找变量。

        3.子作用域能够访问父级作用域,父级作用域无法访问子级作用域

        

闭包

定义:闭包是一种比较特殊的函数,使用闭包能够访问到函数作用域中的变量。其实就相当于闭包是一个作为返回值的函数。

个人理解:1.形成闭包的条件函数嵌套函数,内层函数访问到外层函数的变量。如下代码:

    <script>
        
        function fn1(){
            let i = 10;
            function fn2(){
                console.log(i); // 10
            }
            fn2()
        }
        fn1();
    </script>

闭包的好处:1.创建私有内存,保护变量不被随意更改。

不用闭包的情况:以下代码实现调用一次就加1

    <script>
        let i = 0;
        function fn1(){
            function fn2(){
                i++
                console.log(i);
            }
            return fn2
        }
        const fun = fn1()
        fun()
    </script>

  因为 i 实在全局作用域下,所以可以被任意修改。

 用闭包的情况下:以下代码实现调用一次就加1

    <script> 
        function fn1(){
            let i = 0;
            function fn2(){
                i++
                console.log(i);
            }
            return fn2
        }
        const fun = fn1()
        fun()
    </script>

 这时 i 写在了函数内部,不能被随意更改。

2.外层函数可以访问内存函数的变量

    <script>
        function fn(){
            function fn1(){
                let a = 1;
                console.log(a); 
            }
            return fn1
        }
        fn()() // 1
    </script>

其实就是把内层函数作为返回值给了外层函数

变量提升

变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问。

<script>
  // 访问变量 str
  console.log(str + 'world!'); //  undefindeword

  // 声明变量 str
  var str = 'hello ';
</script>

总结:

  1. 变量在未声明即被访问时会报语法错误

  2. 变量在声明之前即被访问,变量的值为 undefined

  3. let 声明的变量不存在变量提升,推荐使用 let

  4. 变量提升出现在相同作用域当中

  5. 实际开发中推荐先声明再访问变量

函数

函数提升

函数提升与变量提升比较类似,是指函数在声明之前即可被调用。

函数表达式不存在提示现象

<script>
  // 调用函数
  foo()
  // 声明函数
  function foo() {
    console.log('声明之前即被调用...')
  }

  // 不存在提升现象
  bar()  // 错误
  var bar = function () {
    console.log('函数表达式不存在提升现象...')
  }
</script>

函数参数Plus版

动态参数 --- arguments

arguements是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参

注意:箭头函数没有动态参数

使用场景:在不确认实参的情况下。

    <script>
        function fn(){
            console.log(arguments); // [2,2,3,4,4,2,3,4]
        }
        fn(2,2,3,4,4,2,3,4);
    </script>

剩余参数 --- ...(三个点)

...是语法符号,置于最末函数形参之前,用于获取多余的实参。返回的是真数组

    <script>
        function fn(a,b,c,...arr){
            console.log(arr); // [4, 4, 2, 3, 4]
        }
        fn(2,2,3,4,4,2,3,4);
    </script>

也可以获取全部的实参

    <script>
        function fn(...arr){
            console.log(arr); // [2, 2, 3, 4, 4, 2, 3, 4]
        }
        fn(2,2,3,4,4,2,3,4);
    </script>

补充:展开运算符(也是三个点 ... 在数组中)

数组展开,可以获得数组中的所有元素,且不会修改原数组。

作用:求最大最小值;合并数组

求最大值:

    <script>
        let arr = [2,2,3,4,5,2,4,1];
        console.log(...arr); // 2,2,3,4,5,2,4,1
        console.log(Math.max(...arr)); // 5
    </script>

合并数组:

    <script>
        let arr1 = [2,2,3,4];
        let arr2 = [3,2,3,4]
        let arr = [...arr1,...arr2]
        console.log(arr); //[2, 2, 3, 4, 3, 2, 3, 4]
    </script>

箭头函数

箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。

箭头函数的格式:

    <script>
        const fn = () => {
            console.log('老子就是箭头函数');
        }
        fn();
    </script>

1.箭头函数属于表达式函数,所以没有函数提示

2.箭头函数只有一个参数时可以省略圆括号()

        const fn = x => {console.log(x);} // 1
        fn(1);

3.箭头函数函数体只有一行代码时可以省略花括号{}

        const fn = x => console.log(x); // 1
        fn(1);

4.只有一行代码的时候,可以省略return

        const fn = (x,y) => x + y;
        console.log(fn(1,2)); // 3

5.箭头函数可以直接返回一个对象

        const fn = (uname,age) => ({uname:uname,age:age})
        console.log(fn('张三',20)); // 张三 20

注意:箭头函数没有arguments动态参数,只可以使用...剩余参数

    <script>
        const fn = (...arr) => {
            console.log(arr); // [2, 3, 4, 5, 1]
        }
        fn(2,3,4,5,1)

        const fn1 = () => {
            console.log(arguments); // 报错 arguments is not defined
        }
        fn1(3,12,314,21); 
    </script>

箭头函数的this

1.首先箭头函数不会创建自己的this,而是从自己的作用链上找上一层的this指向

粗暴理解:箭头函数的this指向就是上一级作用域中的this指向,如果上一级还是箭头函数,那就继续往上找呗,实在没有就指向window咯!!!

2.DOM事件不推荐使用箭头函数。懂得都懂!

对象方法箭头函数this:

    <script>
        const obj = {
            uname:'张胜男',
            age:20,
            sayHi:()=>{
                console.log(this); // this指向window
            }
        }
        obj.sayHi()

        const obj1 = {
            uname:'李亚男',
            age:21,
            sayHello:function fn(){
                console.log(this); // obj

                const fn1 = () => {
                    console.log(this); // obj
                }
                
                fn1();
            }
        }
        obj1.sayHello()
    </script>

解构赋值

数组解构

语法:赋值运算符 = 左侧的 [ ] 用于批量声明变量右侧数组的单元值被赋值给左侧的的变量

注意:解构之后必须加分号

    <script>
        const arr = [100,200,300];
        const [a,b,c] = arr;
        console.log(a,b,c); //100 200 300
    </script>

交换两个变量的值 不用临时变量

    <script>
        let a = 1;
        let b = 2;
        [b,a] = [a,b];
        console.log(a,b); //2 1
    </script>

数组解构变量和单元值数量

  • 变量多, 单元值少

  • 变量少, 单元值多

  • 剩余参数 变量少, 单元值多

  • 防止 undefined 传递

  • 按需导入赋值

// 1. 变量多, 单元值少 , undefined
const [a, b, c, d] = [1, 2, 3]
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3
// console.log(d) // undefined

// 2. 变量少, 单元值多
const [a, b] = [1, 2, 3]
console.log(a) // 1
console.log(b) // 2

// 3.  剩余参数 变量少, 单元值多
const [a, b, ...c] = [1, 2, 3, 4]
console.log(a) // 1
console.log(b) // 2
console.log(c) // [3, 4]  真数组

// 4.  防止 undefined 传递
const [a = 0, b = 0] = [1, 2]
const [a = 0, b = 0] = []
console.log(a) // 1
console.log(b) // 2

// 5.  按需导入赋值
const [a, b, , d] = [1, 2, 3, 4]
console.log(a) // 1
console.log(b) // 2
console.log(d) // 4

对象解构

语法:赋值运算符 = 左侧的 { } 用于批量声明变量右侧数组的单元值被赋值给左侧的的变量。

注意:属性名和变量名必须一致才可以

    <script>
        const obj = {
            uname:'张三',
            age:20
        }
        const {uname,age} = obj
        console.log(uname,age); //张三 20
    </script>

对象解构变量重新命名

        语法:const { 旧变量名:新变量名} = {变量名:值}

        

    <script>
        const obj = {
            uname:'张三',
            age:20
        }
        const {uname: cname,age} = obj
        console.log(cname,age); //张三 20
    </script>

多级对象解构

    <script>
        const user = {
            uname:'张三',
            friend:{
                ls:'李四',
                zw:'赵五',
            },
            age:18
        }

        const {uname,friend:{ls,zw},age} = user
        console.log(uname,ls,zw,age); // 张三 李四 赵五 18
    </script>

forEach遍历数组

forEach 就是遍历,加强版的数组对象,适合于遍历数组对象。返回的是元素本身

语法:数组.forEach( (数组元素,数组索引) => { } )

    <script>
       const arr = ['red','green','blue'];
       arr.forEach((item,i) => {
        console.log(item); // red green blue
        console.log(i); // 0 1 2
       })
    </script>

filter筛选数组

返回符合条件的新数组

语法:数组.filter( (数组元素,数组索引) => { return 符合条件的元素 } )

const arr = [10, 20, 30]
const newArr = arr.filter(function (item, index) {
    return item >= 20
})
//箭头函数简写
const newArr = arr.filter(item => item >= 20)
console.log(newArr) // [30]

构造函数 

定义:也是函数,主要用来创建具有相同属性名的对象,初始化对象

应用场景:把一些类似的函数封装成一个构造函数,用这个构造函数去创建更多的对象。

规定:1.命名以大写字母开头 2.只能由 new 操作符来执行

构造函数的写法

作用:构造函数是来快速创建多个类似对象

注意:构造函数没有return,会自动返回创建新的对象。因为new了一下


new实例化执行的过程

        1.创建新对象

        2.构造函数this指向这个新对象

        3.执行构造函数代码,修改this,添加新属性

        4.返回新对象

实例成员和静态成员

1.通过构造函数创建的对象是实例对象,实例对象中的属性和方法称为实例成员

2.构造函数的属性和方法称为静态成员(通常是一些共享的属性或方法)

    <script>
       function Goods(uname,price,count){
        this.uname = uname; //实例成员
        this.price = price; //实例成员
        this.count = count; //实例成员
       }
       const mi = new Goods('小米',1999,21);
       mi.id = 1; // 这个id也是实例成员

       Goods.sum = 20; // 静态成员
       Goods.sayHi = () => {  // 静态成员
            console.log('Hi~~~');
       }  
       
    </script>

内置构造函数

Object静态方法

静态方法就是只有构造函数Object可以调用

常用:Object.keys(对象名) --- 获取对象里的属性名 返回一个新数组

           Object.values(对象名) --- 获取对象里的属性值 返回一个新数组

           Object.assign(新对象名,旧对象名) --- 拷贝对象 这是个浅拷贝 

              

    <script>
        const obj = {
            uname:'张三',
            age:18,
            gender:'男'
        }
        
        console.log(Object.keys(obj)); //  ['uname', 'age', 'gender']
        console.log(Object.values(obj)); // ['张三', 18, '男']

        const newObj = {};
        Object.assign(newObj,obj);
        console.log(newObj); // {uname: '张三', age: 18, gender: '男'}
    </script>

扩展提示 数组方法

数组 reduce方法

reduce 返回函数累计处理的结果,经常用于求和等。

数组.reduce( function(累计值,当前元素,[索引号],[原数组]){ return 出来的值就是新的累计值 },起始值 )   ---- 如果起始值不传的话,就会以数组中第一个算。

    <script>
        const arr = [2,2,3,4];
        const res = arr.reduce(function(prev, item){
            return prev + item;
        })
        console.log(res); // 11
    </script>

数组find方法

用在数组对象中,找到自己想要的数据,把符合条件的对象返回出来。

    <script>
        const arr = [
            {
                uname:'小米',
                price:1999
            },
            {
                uname:'华为',
                price:9999
            }
        ]
        let res = arr.find(function(item){
            return item.uname === '小米'
        })
        console.log(res); // {uname: '小米', price: 1999}
    </script>

数组 every和some方法

返回值是true和false

every方法:设定一个条件,数组中的元素必须全部满足才能返回ture

some方法:设定一个条件,数组中的元素只要有一个满足就返回true

    <script>
        const arr = [4,5,4,1,5,6,7];

        let res = arr.every(function(item){
            // console.log(item); // 只打印第一个
            return item >= 3;
        })
        console.log(res); // false


        let res1 = arr.some(function(item){
            // console.log(item); // 全部打印出来
            return item >= 3;
        })
        console.log(res1); // true
    </script>

String常见方法

1. split() 把字符串 转换为 数组 和 join() 相反

        const str = 'red,green,blue';
        const arr = str.split(',');
        console.log(arr); // ['red','green','blue']

2. 字符串的截取 substring(开始的索引号,结束的索引号)

        2.1 如果结束索引号省略,则一直截取到尾部

        2.2 顾头不顾尾(从0开始数数)

        const str2 = '今天又要做核酸了';
        console.log(str2.substring(5,7)); // 核酸

3. 检测是否以某字符开头  startWidth(检测字符串)  返回值 true或false

        3.1 被检测的字符串的开头一定要和检测字符串一致,不然为false

        const str3 = 'Peiqi不是猪';
        console.log(str3.startsWith('P')); // true
        console.log(str3.startsWith('pei')); false

4.判断某个字符是不是包含在一个字符串里面 includes(检测字符串)  返回值 true或false

        4.1 被检测的字符串只要有一个检测字符串一致就为true

        const str4 = 'To be or not to be';
        console.log(str4.includes('to')); // true

Number常见方法

toFixed() 保留几位小数 遵循四舍五入原则

        const num = 10.945;
        console.log(num.toFixed()); // 11
        console.log(num.toFixed(1)); // 11.0
        console.log(num.toFixed(2)); // 10.95
        console.log(num.toFixed(3)); // 10.945

面向对象

面向对象和面向过程

面向过程:面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。

面向对象:面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。

简单的问题可以用面向过程的思路来解决,直接有效,但是当问题的规模变得更大时,用面向对象效率更高点

原型 prototype

定义:每一个构造函数都有一个prototype属性,指向另一个对象,所以也称为原型对象。

作用:1. 共享方法,解决构造函数浪费内存的问题。

           2. 可以固定不变的方法,定义在prototype对象上,方便调用。

构造函数浪费内存是什么?

        答:实例对象调用构造函数里面的复杂数据类型(方法)时,每当有一个实例对象调用时,都要在内存的栈中开辟一个新的地址,但堆中的数据都是一样的。这样大大的浪费了内存。

构造函数和原型对象里的this指向

都指向实例对象

    <script>
        let thatFn;
        let thatPro;

        function Fn(){
            // 构造函数里的this
            thatFn = this;
        }

        Fn.prototype.eat = function (){
            // 原型对象里面的this
            thatPro = this
        }

        let obj = new Fn();
        obj.eat();

        console.log(thatFn === obj); // true  构造函数里的this指向实例对象
        console.log(thatPro === obj); // true 原型对象里面的this指向实例对象
    </script>

原型对象里的constructor属性

作用:指向构造函数

constructor属性的应用:避免以赋值的形式给原型对象添加方法的时候,修改了初始化时的原型对象在内存栈中的地址,丢失了constructor而不能指回构造函数了。这时候只要添加constructor属性让constructor重新指回构造函数即可。

    <script>
        function Fn(uname){
            this.uname = uname;
        }

        // 以对象的形式给原型对象赋值添加方法
        Fn.prototype = {
            eat:function(){
                console.log(this.uname+'爱吃饭');
            },
            drink:function(){
                console.log(this.uname+'爱喝可乐');
            }
        }

        let zs = new Fn('张三');
        console.log(Fn.prototype);
    </script>

  这时候原型对象里面以及没有constructor属性了

    <script>
        function Fn(uname){
            this.uname = uname;
        }

        // 以对象的形式给原型对象赋值添加方法
        Fn.prototype = {
            // 设置constructor属性重新指回构造函数
            constructor:Fn,
            eat:function(){
                console.log(this.uname+'爱吃饭');
            },
            drink:function(){
                console.log(this.uname+'爱喝可乐');
            }
        }

        let zs = new Fn('张三');
        console.log(Fn.prototype);
    </script>

  设置constructor属性重新指回构造函数

对象原型__proto__

对象原型在实例对象里面,指向构造函数的原型对象prototype。构造函数里没有对象原型,

对象原型里面也有constructor属性并且指向构造函数

构造函数、原型对象、实例对象三者的关系

1. 实例对象是构造函数创造出来的。

2. 原型对象是构造函数里面特有的。

所以原型对象和实例对象都是构造函数搞出来的,粗暴理解成构造函数为爸爸,原型对象为大儿子,实例对象为二儿子。

三者的关系:

        构造函数指向原型对象,同时指向实例对象。原型对象里的constructor属性指向构造函数;实例对象里的对象原型指向原型对象,constructor属性指向构造函数。

原型链

原型链是一种查找规则:

调用A实例对象的方法,如果没有就往A原型对象找,如果没有就往Object原型对象上找,如果找到最后一层Object原型对象的对象原型就是null,这样就会形成一个查找链条,这就是原型链。

查找规则:

        当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性,如果没有就查找它的原型(就是__proto__指向的prototype原型对象),如果还没有找到就查找原型对象的原型(object的原型对象),依次类推一直找到object的原型对象上的对象原型为止(null)。

作用:

        提供一个查找规则,找到相应的方法。

浅拷贝和深拷贝

为什么会有拷贝?

        因为直接赋值对象会有问题,赋值是把对象的地址给另一个对象,相当于两个对象共用一个地址,所以修改新对象的值也会改变旧对象的值。拷贝就可以解决这种问题。

赋值和拷贝的区别?

        赋值是两个对象共用一个地址,修改新对象的值,旧对象也会被修改。

        拷贝是新对象拷贝旧对象的地址,都各有自己的地址,所以新对象修改自己的值不会影响旧对象的值。

浅拷贝

定义:拷贝对象的地址值

方法:拷贝对象:Object.assign(新对象,旧对象)    展开运算符{...被拷贝的对象obj}

           拷贝数组:Array.prototype.concat()  [...arr]

浅拷贝适合用于只有简单数据类型的对象,没有复杂数据类型的对象,如果里面有复杂数据类型拷贝的是地址。

    <script>
        const oldObj = {
            uname:'张三',
            age:18,
            hobby:['唱','跳','rap'],
            star:{
                sname:'才虚困'
            }
        }

        const newObj = {}
        Object.assign(newObj,oldObj);
        // 新对象修改简单数据类型不会影响,修改复杂数据类型会影响。
        newObj.uname = '李四';
        newObj.hobby.push('篮球');
        console.log('旧对象',oldObj);
        console.log('新对象',newObj);
    </script>

递归函数

定义:函数内部自己调用自己,这个函数就是递归函数。

作用:递归函数的作用和循环效果类型。

由于递归很容易发生 栈溢出 错误,所以必须加要加退出条件 return

//需求:让这个函数执行6次就结束 
    // 1.有个变量计数
    // 2.判断条件
    // 3.变量的变化量

let i = 1
function fn() {
    console.log(`这是第${i}次`)
    if (i >= 6) {
        return
    }
    i++
    fn()
}
fn()

深拷贝

深拷贝和浅拷贝的区别:

        如果被拷贝的对象里面有复杂数据类型,新对象修改复杂数据类型的值,旧对象仍然会受到影响,而深拷贝可以解决这个问题。

利用递归函数实现深拷贝

实现步骤:

1.深拷贝是地址值相互不影响,如果需要做的话,需要用到函数的递归

2.普通类型的不用管,直接用forin就可以,但是如果是数组的需要再次调用递归函数

3.如果遇到对象类型的也需要再次调用递归函数,如果是用instanceof来判断是否是数组对象的需要先数组在对象

    <script>
        const obj = {
            uname: '张三',
            age: 18,
            hobby: ['唱', '跳', 'rap'],
            star: {
                sname: '才虚困'
            }
        };
        const o = {};
        
        function deepCopy(newObj,oldObj){
            for(let k in oldObj){
                if(oldObj[k] instanceof Array){
                    newObj[k] = [];
                    deepCopy(newObj[k],oldObj[k])
                }
                if(oldObj[k] instanceof Object){
                    newObj[k] = {};
                    deepCopy(newObj[k],oldObj[k])
                }
                else{
                    newObj[k] = oldObj[k]
                }
            }
        }
        deepCopy(o,obj);

        
        o.star.sname = '王一博';
        o.hobby[0] = '篮球';
        console.log('旧对象',obj);
        console.log('新对象',o);
    </script>

 

利用lodash实现深拷贝

Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。

<script src="./lodash.min.js"></script>
<script>
    const obj = {
        uname: '张三',
        age: 18,
        hobby: ['唱', '跳', 'rap'],
        star: {
             sname: '才虚困'
          }
    }

    const o = _.cloneDeep(obj)
    console.log(o)
</script>

利用JSON实现深拷贝

  • JSON.stringify() //对象转换为字符串

  • JSON.parse() //字符串转换为对象

    <script>
        const obj = {
            uname: '张三',
            age: 18,
            hobby: ['唱', '跳', 'rap'],
            star: {
                sname: '才虚困'
            }
        };

        // 第一步  利用 JSON.stringify() 把对象转换成JSON字符串
        const str = JSON.stringify(obj);
        // 第二步  利用 JSON.parse() 把JSON字符串转换成对象
        const o = JSON.parse(str);
        console.log(o);
    </script>

this相关知识

普通函数的this指向

普通函数中:谁调用我,this就指向谁。

dom事件里的函数中:谁绑定事件,this就指向谁。

对象里的函数中:指向对象

// 普通函数:  谁调用我,this就指向谁
console.log(this)  // window
function fn() {
    console.log(this)  // window    
}
window.fn()
window.setTimeout(function () {
    console.log(this) // window 
}, 1000)
document.querySelector('button').addEventListener('click', function () {
    console.log(this)  // 指向 button
})
const obj = {
    sayHi: function () {
        console.log(this)  // 指向 obj
    }
}
obj.sayHi()

箭头函数中的this指向

箭头函数内不存在this,this的指向问题看上一级作用域的this指向。过程:向外层作用域,一层一层查找this,直到有this的定义。

箭头函数不适应:构造函数、原型函数、字面量对象中的函数、dom事件函数

箭头函数适应:需要使用上层this完成需求的地方

call方法改变this指向

作用:call方法调用函数,同时指定被调用函数中this的值。

语法:函数名.call(thisArg,arg1,arg2,....) // 这里的参数是字符串

    <script>
        const obj = {
            uname:'pink'
        }
        function fn(x,y){
            console.log(this); // window
            console.log(x + y); // 3
        }
        // 改变this指向
        fn.call(obj,1,2)  // 此时函数里的this指向了Object
    </script>

apply方法改变this指向

作用:apply方法调用函数,同时指定被调用函数中this的值。

语法:函数名.apply(thisArg,arg1,arg2,....) // 这里的参数是数组

call和apply区别:

        都是调用函数,都能改变this指向。

        参数不一样,apply传递的必须是数组。

    <script>
        const obj = {
            uname:'张三'
        }
        function fn(x,y){
            console.log(this); // window
            console.log(x + y); // 3
        }
        fn.apply(obj,[1,2])  // 此时函数里的this指向了Object
    </script>

bind方法改变this指向

作用:bind方法不会调用函数。但是能改变函数内部的this指向

语法:函数名.bind(thisArg,arg1,arg2,....) // 返回值是原函数的拷贝(新函数)

    <script>
        const obj = {
            uname:'张三'
        }
       function fn(){
        console.log(this); // Object --- { uname:'张三 }
       }
       // 1. bind 不会调用函数
       // 2. 能改变this指向
       // 3. 返回值是个函数  但是这个函数里的this是更改过的obj了
       fn.bind(obj)();
    </script>

总结:call和apply以及bind三者的区别

相同点:都可以改变函数内部的this指向

不同点:1. call和apply会调用函数,并且改变函数内部的this指向。

               2. call和apply传递的参数不一样,call传递参数aru1,aru2;apply必须传递数组。

               3. bind 不会调用函数,可以改变函数内部this指向。

节流和防抖

节流:就是指连续触发事件但是在n秒中只执行一次函数。

防抖:就是指触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值