js进阶知识点

本文详细介绍了JavaScript中的闭包,包括其定义、作用以及可能导致的内存泄漏问题。讨论了ES6的新特性,如剩余参数、箭头函数和变量提升。深入讲解了构造函数、原型对象和原型链,以及面向对象编程的基本概念。此外,还涵盖了函数的this指向、深拷贝方法和对象的创建与继承。
摘要由CSDN通过智能技术生成

闭包是什么

闭包就是函数的嵌套,内层函数访问了外层函数的局部变量

闭包有什么用

实现变量私有化,让外面的人无法访问内部变量

闭包会产生什么现象和问题

  1. 内存泄漏
  2. 本该被释放的变量,无法及时释放,会存在闭包空间中

变量提升(仅存在于var声明变量)

  1. 变量提升出现在当前作用域的最前面
  2. 提升时,只提升变量声明,不提升变量赋值
  3. let/const声明的变量不存在变量提升
  4. 实际开发中推荐先声明在访问变量

函数提升(函数提升与变量提升比较相似)

  1. 函数提升只提升声明,不提升调用
  2. 函数表达式不存在提升的现象

函数声明
function fn(){}

函数表达式,匿名函数
var fn = function(){}

arguments对象的使用

'use strict'
        //需求:求和函数,参数不限数量
        //解决方案:arguments伪数组
        function sum(){
            //开启严格模式,无法使用arguments
            /*
                arguments非常特殊,只存在于function函数中
                这个伪数组中装着所有的实参
                伪数组用不了数组的方法 forEach            
            
            */
            console.log(arguments);
            let sum = 0;
            for (let index = 0; index < arguments.length; index++) {
                sum +=arguments[index]                
            }
            return sum
        }

        console.log(sum(1,2));
        console.log(sum(1,2,3));

        /*
            为什么现在不用arguments了?
            1.是可变参数的出现ES6新特性
            2.性能原因
        */
       function fn1(a){
        arguments[0] = 50
        console.log(arguments[0])//50
        //arguments是动态伪数组,如果内部的数据发生改变,会自动同步到形参
        console.log(a);//50
       }
       fn1(10)

       /*
        聊一聊arguments这个对象
        arguments就是一个伪数组,仅存在于function中,它的
        作用是装载着所有的实参,因为arguments是动态伪数组,
        数据变化带来的性能损耗较大,每次都会同步更新所有参数,
        如果参数过多则会出现问题,在开启严格模式后就会彻底禁用arguments,
        现在开发中基本不会arguments了,可以使用可变参数完全替代arguments的功能
       */

剩余参数(…) ES6语法新特性 2015年提出

  1. …是语法符号,置于最末函数形参之前,用于获取多余的实参
  2. 借助…获取的剩余实参,是个真数组
  3. 剩余参数支持箭头函数,开发过程中提倡多使用剩余参数

剩余参数:函数参数使用,把多个元素收集起来生成一个真数组(凝聚)
展开运算符:将数组展开成各个元素(拆散)

箭头函数(重要)

  1. 当箭头函数只有一个参数时,可以省略参数的小括号,其余个数不能省略(没有参数也需要加上小括号)
  2. 当箭头函数的函数体只有一句代码 可以省略函数体的大括号,这句代码就是返回值(可以不用写return)
  3. 如果返回的是个对象,则需要把对象用小括号包裹
  4. 箭头函数里面没有arguments,但是有剩余参数

箭头函数中的this指向

function函数中的this指向调用者
箭头函数的this指向和当前作用域的this指向一样,它没有自己的this指向
箭头函数的this指向取决于当前箭头函数定义的位置,不看是谁调用的箭头函数
一般事件处理源函数不要用箭头函数,因为普通的function函数,更容易让我们找到事件源
优先使用箭头函数,若不行则使用function函数

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

        let fn1 = (a,b) => (a+b)
        console.log(fn1(1,2));

        function fn2(){
            console.log(this);//window
        }

        fn2()

        setTimeout(()=>console.log(this),1000)//window

        document.querySelector("button").addEventListener('click',function(){
            console.log(this);//button
            setTimeout(()=>{
                console.log(this);//button
            },1000)
        })

构造函数

构造函数是一种特殊的函数,专门用于帮助程序员创建对象的
构造函数本质就是普通函数,只有在使用new关键字调用它的时候,就被称为构造函数
定义时和普通函数没有任何区别,只有在调用时才能决定是否是构造函数
特点:

  1. 调用构造函数会自动创建一个新对象
  2. 构造函数中的this指向这个新对象
  3. 执行函数的代码,可以给当前创建的新对象添加属性
  4. 默认返回这个新对象,不需要写return

构造函数new实例化执行过程

  1. 创建空对象
  2. 构造函数this指向新对象
  3. 执行构造函数代码
  4. 返回新对象

实例成员(属性和方法)写在谁身上?

实例对象的属性和方法即为实例成员(new 构造函数就是实例对象)
实例对象相互独立,实例成员当前实例对象使用

静态成员(属性和方法)写在谁身上?

构造函数的属性和方法被称为静态成员
静态成员只能构造函数访问

function Person(name,age){
            //实例成员(属性和方法)
            this.name = name; 
            this.age = age;
            this.dance = function(){
                console.log("跳舞");
            }
            
        }
        const zs = new Person("张三",18)//实例对象
        zs.dance()
        console.log(zs);//实例成员只能当前实例对象使用
        //静态成员(属性和方法)
        Person.hobby = '看书'
        Person.swim = function(){console.log('游泳');}
        console.log(Person.hobby);
        Person.swim();//静态成员只能构造函数访问

Object静态方法

 let person = {
            name:'张三',
            age:19
        }
        for(let k in person) {
            console.log(k);//键
            console.log(person[k]);//值
        }
        //获取对象中所有key Object.keys() 返回数组
        console.log(Object.keys(person));
        Object.keys(person).forEach(k=>console.log(k));

        //获取对象中所有的值 Obejct.values() 返回数组
        console.log(Object.values(person));
        Object.values(person).forEach(k=>console.log(k));

        //Object.assign()
        //将源对象的所有属性复制给目标对象 返回新对象
        //参数一:目标对象 参数二:源对象
        const o = Object.assign({},person)
        console.log(o);

常用数组方法
静态方法Array.from()将伪数组转换成真数组
在这里插入图片描述

常用字符串方法
在这里插入图片描述
number.toFixed(x) toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。

什么是面向过程?
按照我们分析好了的步骤,按照步骤一步一步的解决问题
什么是面向对象
以对象功能来划分为一个个对象,然后由对象之间分工和合作
两种编程思想的特点

面向过程性能高,但是不灵活,复用性低
面向对象灵活,易扩展,易维护,但是性能比面向过程低

构造函数方法很好用,但是存在浪费内存的问题

function Person(name,age){
            this.name = name;
            this.age = age;
            this.hi = function(){console.log('hi');}
        }
        const zs = new Person('zs',18)
        const ls = new Person('ls',18)
        /*
            原型对象:
            所有的构造函数都会有一个属性:prototype
            他是一个对象,我们称它为原型对象
            特点:
            1.所有通过构造函数创建出来的实例对象,都可以访问原型对象中的成员
            2.实例对象创建时,不会重复创造原型对象,每个构造函数的原型对象是唯一的
            3.对象会优先找自己身上的属性和方法,找不到才去原型上查找属性和方法,就近原则
        */
        console.log(Person.prototype);
        Person.prototype.name = '韩伟'
        //原型对象中方法的this指向:调用者,实例对象
        //1.构造函数不能是箭头函数,因为箭头函数的this指向和当前环境有关
        //2.原型的函数要根据情况判断,如果用了箭头函数意味着this不再指向实例对象
        Person.prototype.sayHi = function(){console.log('你好');console.log(this);}

        zs.sayHi()
        ls.sayHi()
        console.log(zs.hi === ls.hi);
        console.log(zs.sayHi ===ls.sayHi);
        const arr = ['12']
        console.log(arr);
        console.log(Array.prototype);
        /*
            原型对象是什么?
            prototype,每个构造函数都有原型对象
            原型对象的作用是什么?
            可以把那些公共的属性和方法,直接定义在prototype对象上
            实例对象可以直接访问原型对象中的属性和方法
            这些属性和方法不会多次内存中创建,从而节约空间
        */

constructor属性的作用

function Person(name,age){
            this.name = name;
            this.age = age;
        }
        Person.prototype.hobby = function(){console.log(this);}
        //constructor属性在原型对象里面
        //constructor属性的作用是指向该原型对象的构造函数
        console.log(Person.prototype);
        console.log(Person.prototype.constructor);

        //批量往原型对象上加方法和属性
        Person.prototype = {
            constructor:Person,
            sing(){console.log('唱歌');},
            sing1(){console.log('唱歌');},
            sing2(){console.log('唱歌');}
        }
        console.log(Person.prototype);

对象中的原型__proto__
在这里插入图片描述

function Person(name, age) {
            this.name = name;
            this.age = age;
            this.hi = function () { console.log('hi'); }
        }
        const zs = new Person('zs', 18)
        const ls = new Person('ls', 18)
        console.log(Person);
        console.log(zs);
        console.log(Person.prototype);
        //__proto__不是标准属性,在浏览器已经将其弱化显示[[Prototype]]:
        console.log(zs.__proto__);
        console.log(Person.prototype == zs.__proto__);
        console.log(Person.prototype === zs.__proto__);
        /*
            prototype是什么?哪里来?
            原型对象
            构造函数都自动有原型对象
            __proto__属性是什么?在哪里?指向谁?
            原型
            在实例对象里面
            指向prototype原型对象,这样实例对象就可以访问原型对象里面的方法
        */

原型链
在这里插入图片描述

function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        const zs = new Person('zs', 18)
        Object.prototype.sayHi = function(){console.log("Object上的prototype");}
        zs.sayHi()
        console.log(zs.__proto__ === Person.prototype);true
        console.log(Person.prototype.__proto__ === Object.prototype);//true
        console.log(Object.prototype.__proto__);//null
        /*
            原型链(对象属性的查找规则)
            实例对象中找属性和方法时,如果找不到,会找__proto__
            原型对象中也找不到,再寻找原型对象的__proto__
            逐级向上查找,直到Object的原型对象为止(null),如果都找不到则没有
        */

instanceof运算符

function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        const zs = new Person('zs', 18)
        /*
        instanceof运算符返回的是布尔值
        instanceof一般可用于判断具体的引用数据类型,比typeof更准确,typeof一般只用于判断基本数据类型
        实例对象 instanceof 构造函数 ,判断构造函数的原型对象是否在实例对象的原型链上
        也可以通俗理解 当前的对象,是否是构造函数原型底下的孩子
        */
       const arr = '123'
       const arr1 = []

       console.log(typeof arr);
       console.log(typeof arr1);//typeof判断不了引用数据类型
       console.log(arr1 instanceof Array);

原型继承

function Man(){}
        function Woman(){}
        function Person(){
            this.age = 2
        }
        Person.prototype.eat=function(){console.log('吃饭');}
        Man.prototype = new Person()
        Man.prototype.constructor = Man
        Woman.prototype = new Person()
        Woman.prototype.baby=function(){console.log('wa')}
        const m1 = new Man()
        const w1 = new Woman()
        console.log(Man.prototype);
        m1.eat()
        w1.eat()
        w1.baby()
        /*
            原型继承总结:
            1.创建父级构造函数
            2.将所有公共的方法放到父级的原型对象上
            3.将子级构造函数的原型对象  指向 父级构造函数创建的实例对象
            注意:因为对象覆盖了原型对象,所有再把constructor指回当前构造函数
            Man.prototype = new Person()
            Man.prototype.constructor = Man
        */

完整图解
在这里插入图片描述

浅拷贝

const obj = {
            name:'zs'
        }
        //对象浅拷贝
        //1.Object.assign() 
        //const newObj = Object.assign({},obj)
        //2.展开运算符
        const newObj = {...obj}
        obj.name = 'ls'
        console.log(obj);
        console.log(newObj);

        const arr = ['1','2']
        //数组浅拷贝
        //1.concat方法合并一个空数组
        // const newArr = [].concat(arr)
        //2.展开运算符
        const newArr = [...arr]
        arr[1] = '3'
        console.log(arr);
        console.log(newArr);
        /*
            总结:
            对象和数组的浅拷贝就是把对象里的属性和值复制一份,放到新对象中存储,
            新老对象是不同的地址值,所以修改其中一个对象,不会影响另一个对象的属性

            问题:
            浅拷贝就是只拷贝对象第一层的数据,如果全部都是基本数据类型则没有任何问题
            只要有引用数据类型的数据,将会将其地址值复制一份,导致两个对象的属性共用同一个对象
        */

深拷贝的两种方法

const obj = {
            name:'zs',
            family:{
                name:'ls'
            },
            love:undefined,
            play(){
                console.log('玩耍');
            }
        }
        //需求:拷贝一个新对象,修改对象内的引用数据类型属性,也不会对原对象有任何影响
        //1.利用JSON的序列化和反序列化实现
        //序列化:JSON.stringify(obj)
        //反序列化:JSON.parse(obj)
        /*注意事项:
            使用JSON.stringify和JSON.parse进行深拷贝 无法拷贝undefined和function
        */
        const newObj = JSON.parse(JSON.stringify(obj))
        console.log(obj === newObj);
        obj.family.name = '王五'
        console.log(obj);
        console.log(newObj);
        //2.js库lodash里面的_.cloneDeep内部实现了深拷贝
        /*
            参数:需要深拷贝的对象
            返回值:拷贝好的新对象
            优点:连同undefined和function函数一并拷贝过来
            缺点:需要额外引入一个第三方库,增加项目体积
        */

递归实现深拷贝

/*
            深拷贝思路:
            1.深拷贝的核心就是利用函数递归
            2.封装函数,里面先判断拷贝的是数组还是对象
            3.然后开始遍历
            4.如果属性值是引用数据类型(比如数组或者对象),则再次递归函数
            5.如果属性值是基本数据类型,则直接赋值
        */
        const obj = {
            name:'zs',
            family:{
                name:'ls'
            },
            love:undefined,
            play(){
                console.log('玩耍');
            }
        }

        function cloneDeep(oldObj){
            //1.先判断要拷贝的是对象还是数组
            let newObj = Array.isArray(oldObj) ? [] : {}
            //2.遍历老对象,把属性复制到新对象中
            for (const k in oldObj) {
                newObj[k] = typeof oldObj[k] === 'object' ? cloneDeep(oldObj[k]) : oldObj[k]
            }
            return newObj
        }
        const newObj = cloneDeep(obj)
        obj.family.name = 'ww'
        console.log(obj);
        console.log(newObj);

可以改变this指向的三种方法

function fn(a,b){
            console.log(this);
            console.log(a+b);
        }
        fn(1,2)//window 3

        const obj = {
            name:'zs'
        }
        fn.call(obj,2,3)//obj 5
        //call() 方法是所有函数都能使用的方法
        //1.调用函数
        //2.修改函数内部的this指向
        //参数1:this要指向的对象
        //参数2~参数n(剩余参数)

        fn.apply(obj,[1,2]) //obj 3
        //apply()参数1:this要指向的对象
        //参数2:必须是数组,可以把原方法需要的参数全部放到数组中一起传入
        //apply与call的区别仅仅只是传参方式不同

        const newFn = fn.bind(obj,1,2)
        newFn()//obj 3
        //参数1:this要指向的对象
        //参数2~参数n(剩余参数)
        //返回值:新函数,该函数内部的this指向参数1

this的指向
在这里插入图片描述

节流防抖
防抖:单位时间内,频繁触发事件,只执行最后一次
节流:单位时间内,频繁触发事件,只执行一次
手写防抖函数:只要有定时器就要清除定时器后开启
手写节流函数:判断内存中有没有正在开启的定时器,如果有就不能再开了,如果没有才开启定时器,定时器执行时记得清除定时器id标识

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值