JS实用小知识

106 阅读11分钟

1.防抖函数

//在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时  场景:输入框联想输入
//防抖函数,可用于登录使用,在用户输入完最后的时间为准,隔着传入时间执行一次  
//运用了闭包 推荐使用lodash中的_.debouch()和_.throttle()
<input type="text" id="ipt">
function debouch(cb, delay = 1000) {
            let timer = null
            return function () {
                clearTimeout(timer)
                timer = setTimeout(() => {
                    cb.apply(this, arguments)//arguments无所谓,就是事件源
                }, delay)
            }
        }
        const ipt = document.querySelector("#ipt")
        let newFn = debouch(function (e) {
            console.log(e.target.value);
        }, 1000)
        ipt.addEventListener("input", newFn)

2.节流函数

//节流函数 每隔固定时间执行一次,如水龙头滴水一样,jd搜索就是这么做的 抢购
  function throttle(cb, delay=1000) {
            //设置节流阀
            let lock = true
            return function () {
                if (lock) {
                    setTimeout(() => {
                        cb.apply(this, arguments)
                        lock = true
                    }, delay)
                    lock = false
                } else {
                    return false
                }
            }
        }
        const ipt = document.querySelector("input")
        ipt.addEventListener("input", throttle(function (e) {
            console.log(e.target.value);
        }, 500))

3.拷贝

<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
 const user = {
            name: "小貂蝉",
            age: "18",
            show: function () { },//使用JSON.parse(JSON.stringify())进行深拷贝会丢失
            a: undefined,//会丢失
            b: Infinity,//null
            c: -Infinity,//null
            date: new Date(),//对象格式被处理成字符串
            reg: /abc/,//{}
            company: {
                name: "因你太美",
                address: "彩云之南"
            }
        }
        //JSON.parse(JSON.stringify(user))实现深拷贝
        let user1 = JSON.parse(JSON.stringify(user))
        let user2 = _.cloneDeep(user)
         // 浅拷贝只拷贝一层
        const info = {
            name: "赛丽亚",
            age: 20
        }
        //for...in 循环
        let tempInfo = {}
        for (let key in info) {
            tempInfo[key] = info[key]
        }
        //Object.assign
        const tempInfo2 = Object.assign({}, info)

4.ES5,ES6的类

//ES5
function Person(name, age) {//构造函数
            this.name = name,
            this.age = age
        }
        Person.prototype.eat = function () {//往原型上挂方法
            return "我是原型上的方法"
        }
        Person.way = function () {//往自己身上挂方法,只有类自己才可以使用
            return "我是静态方法"
        }
        const son = new Person("小貂蝉", 20)
        console.log(son);
//ES6
//申明一个类 class是关键字
   class Person { num = 10;//属性值写死的,会自动成为实例对象(自动挂在new出来的实例对象上) 
            constructor(name, age) {//构造器
                this.name = name,
                this.age = age
            }
            eat() {
                return "我是原型方法"
            }
            static way() {
                return "我是静态方法"
            }
        }
        const son = new Person("李寻欢", 22)
        console.log(son);

5.继承

原型继承

  
        //把父类的实例作为子类的原型
        //缺点 子类的实例共享了父类构造函数的引用属性 不能传参
        let person = {
            friends: ["a", "b", "c"]
        }
        let p1 = Object.create(person)
        p1.friends.push("子类新增") //子类的实例共享了父类构造函数的引用属性
        console.log(person);// {friends: ["a", "b", "c","子类新增"]}  
        //子类的实例共享了父类构造函数的引用属性
   
     

组合继承

   //在子函数中运行父函数,但是要利用call把this指向改变一下,
        //让子函数的prototype指向new Father(),使Father的原型中的方法也得到继承,
        //最后改变Son的原型中的constructor
        //缺点 调用了两次父函数的构造函数,造成了不必要的消耗,父类方法可以复用
        //优点 可以传参,不共享父类引用属性 
        function Father(name) {
            this.name = name
            this.hobby = ["唱", "跳", "篮球"]
        }
        Father.prototype.getName = function () {
            console.log(this.name);
        }
        function Son(name, age) {
            Father.call(this, name)
            this.age = age
        }
        Son.prototype = new Father()
        Son.prototype.constructor = Son
        let son = new Son("哥哥", 20)
        console.log(son);//Son{name:"哥哥",hobby = ["唱", "跳", "篮球"],age:20}

寄生继承

  function Person(name, age) {
            this.name = name
            this.age = age
        }
        Person.prototype.eat = function () {
            return "干饭"
        }

        function Student(name, age, hob) {
            // this.name = name
            // this.age = age
            //第一步 继承父类的属性
            Person.call(this, name, age)
            this.hob = hob
        }
        //第二步,让子类的显示原型成为父类的实例对象
        Student.prototype = Object.create(Person.prototype)
        //找回构造器
        Student.prototype.constructor = Student
        Student.prototype.sex = "女"


        function Monkey(name, age, sno) {
            // this.name = name
            // this.age = age
            Person.call(this, name, age)
            this.sno = sno
        }
        Monkey.prototype = Object.create(Person.prototype)
        Student.prototype.constructor = Student
        Monkey.prototype.job = function () {
            return "cv攻城狮"
        }
        let s = new Student("小貂蝉", 20, "唱跳")
        let m = new Monkey("李寻欢", 22, "rap篮球")
        console.log(s);
        console.log(m);

寄生组合式继承

 //寄生组合式继承
        function Father(name) {
            this.name = name
            this.hobby = ["唱", "跳", "篮球"]
        }
        Function.prototype.getName = function () {
            console.log(this.name);
        }
        function Son(name, age) {
            Father.call(this, name)
            this.age = age
        }
        Son.prototype = Object.create(Father.prototype)
        Son.prototype.constructor = Son
        let son = new Son("小貂蝉", 20)
        console.log(son);//Son{name:"哥哥",hobby = ["唱", "跳", "篮球"],age:20}

ES6继承

class Father {
            constructor(name, age) {
                this.name = name
                this.age = age
            }
            eat() {
                return "吃鸡蛋"
            }
            static hobby() {
                return "唱跳rap篮球"
            }
        }
        class Son extends Father {
            constructor(name, age, tel) {
                super(name, age)//super必须写在第一行,继承父类的属性和方法
                this.tel = tel
            }
            study() {
                return "太美辣"
            }
        }
        const s = new Son("只因", 22, 10001)
        console.log(s);

6.this指向

  //全局指向window 
        console.log(this);
        //函数中的this,谁调用指向谁
        function fn() {
            console.log(this);
        }
        fn()//window 相当于window.fn()
        //对象中的方法的this,谁调用指向谁
        const obj = {
            name: "小貂蝉",
            age: 20,
            show: function () {
                console.log(this);
            }
        }
        obj.show() //obj这个对象
        //构造函数中的this,指向实例对象
        function Person(name, age) {
            this.name = name
            this.age = age
            console.log(this);
        }
        const p = new Person("李寻欢", 22)
        //事件处理函数中的this,指向事件源
        btn.addEventListener("click", function () {
            console.log(this);//事件源
        })
        //定时器中的this,指向window
        setTimeout(function () { 
            console.log(this);//过一秒自己调用
        }, 1000)
        //箭头函数中的this指向上一级(箭头函数没有this,它的this绑定定义函数时所处的作用域)
        btn.addEventListener("click", () => {
            console.log(this);//window 
        })

7.递归

1.函数自己调用自己 2.需要临界条件,有出口 优点:机构清晰,可读性强(斐波那契数列,兔子函数(F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 3))) 缺点:运行效率较低,占用内存,容易爆栈,造成内存溢出

        //阶加求和
        //自己调用自己,有临界点给出口
        function sum(num) {
            if (num === 1) {
                return 1
            } else {
                return num + sum(num - 1)
            }
        }
        let r = sum(5)
        console.log(r);

8.Promise

Promise是同步的,Promise 是异步编程(可以解决回调地狱)的一种解决方案,es6新增的

  • 静态方法
    • resolve()//让状态从进行中pending==》已成功
    • reject()//让状态从进行中==》已失败
    • all()//用途:可以并发多个ajax,同时执行多个Promise,每个都成功,才是成功,才可以拿到最终的结果是一个数组,有一个失败,就直接结束
    • allSettled()//不管成功失败返回一个对象数组,包含状态(成功或者失败)及其值
    • any()//只有有一个Promise成功,立即返回
    • race()//最快的切换状态完成就结束,无论是结束还是失败
  • 原型方法
    • then() //成功(走resolve)
    • catch()//捕获错误 失败(走reject)
    • finally()//无论成功失败都会走
const p=new Promise((resolve,reject)=>{
    if(true){
        resolve("成功")
    }else{
        reject("失败")
    }
})
//p.then返回的是一个pimise对象
p.then(res=>{
    console.log(res)//上一步调用了resolve,就会触发这个函数
},err=>{
    console.log(err)//上一步调用了reject,就会触发这个函数
})
//--------------------
p.then(res=>{
    console.log(res)//上一步调用了resolve,就会触发这个函数
})
 .catch(err=>{//捕获错误
    console.log(err)//上一步调用了reject,就会触发这个函数 和.then的第二参数回调是一样的功能
})
.finally(()=>{
    console.log("无论成功失败,都会走一次")
})
//----------------------
const p1=new Promise((resolve,reject)=>{
    setTimeout(()=>{
     resolve("1")   
    },1000)
})
const p2=new Promise((resolve,reject)=>{
    setTimeout(()=>{
     resolve("2")   
    },2000)
})
const p3=new Promise((resolve,reject)=>{
    setTimeout(()=>{
     resolve("33")   
    },5000)
})
Promise.all([p1,p2,p3]).then((value)=>{
    console.log(value)//5秒以后都成功返回的数据["1","2","3"] 并发发送ajax
})

Promise是同步任务,promise.then(catch,finally)是微任务,async/await也是微任务

9. async/await

是Promise对象的语法糖,是ES7新增的,可以配合try/catch捕获错误 async是声明了这函数中有异步操作,会返回一个Promise对象(同步任务) await是用来等待后面表达式的完成

async function fn(){
   let p= new Promise((resolve,reject)=>{
        reject("Error")
    })
    try{
     let res= await p  
    }catch(e){
        console.log(e)
    }
}

10.原型与原型链

作用:添加共享方法 原型

  • 每个函数(特指构造函数),都有一个属性prototype就是原型(显示原型)
  • prototype是一个对象,添加在里面的属性和方法,被所有实例对象所共享
  • 实例对象(new出来的),都有一个_proto__(隐式原型),指向构造函数的prototype
  • prototype有一个属性constructor(构造器),指向构造函数本事

原型链

  • prototype也是一个实例对象,也有一个_proto__指向Object.prototype
  • Object.prototype也是一个实例对象,也有一个_proto__指向null
  • Object.prototype也有一个属性constructor,指向Object本身

终极无敌蛇皮原型链.png

11.闭包和垃圾回收机制

函数跨作用域访问变量,就会形成闭包,闭包是一种作用域的体现。父函数嵌套子函数,子函数跨作用域访问父函数的变量,把子函数返回或挂载在全局。可以实现早期的模块化,闭包可以解决循环定时器问题,循环绑定事件问题;闭包的优点:把变量隐藏在函数内部,变量私有化,避免全局污染,缺点:过多使用闭包,形成闭包的变量不会被释放,造成内存开销过大,甚至内存泄漏

//第一种
function spendMoney=(function(){
   let money=500
   function spendMoney(){
      console.log(money)
      money-=100
   }
    //把子函数返回
    return spendMoney
})
spendMoney();
//第二种
(function(){
    let money=500;
    function spendMoney(){
       console.log(money)
       money-=100
   }
    //把子函数挂在全局window上
    window.spendMoney=spendMoney 
})()

小憩一下,原型链,闭包,Promise,江湖人称自然界的'珠穆朗玛峰,乔戈里峰,干城章嘉', 翻过这三座山,他们就会听到你的故事

12.call,apply和bind

都是改变this指向,第一个参数是谁,this指向谁

函数体.call()立即执行 第二个参数是罗列式

函数体.apply()立即执行 第二个参数是一个数组

函数体.bind()不会立即执行,会返回一个新的函数,所以需要调用(后面加个括号,函数体.bind()())才能执行;第二个参数也是罗列式

let arr=[1,8,5,2,3]
Math.max.apply(null,arr)//8
let name="张三",age="18";
let obj={
    name="李四",
    objAge:this.age,
    myFn:function(from,to){
        console.log(this.name+"年龄"+this.age,"来自"+from+"去往"+ to);
    }
}
let db={
    name:"小貂蝉"age:"20"
}
obj.myFn.call(db,"成都""上海")//小貂蝉年龄20来自成都去往上海
obj.myFn.apply(db,["成都","上海"])//小貂蝉年龄20来自成都去往上海
obj.myFn.bind(db,"成都""上海")()//小貂蝉年龄20来自成都去往上海
obj.myFn.bind(db,["成都","上海"])()//小貂蝉年龄20来自成都,上海去往undefined

13.手写call,apply和bind

**call**
Function.prototype.maCall=function(context=window){
//先判断调用的maCall是不是一个函数,this指向调用者(调用maCall的)
if(typeof this !=="function"){
throw new TypeError("Not a Function")
}
//保存this
context.fn=this
//保存参数
let args= Array.from(arguments).slice(1)//Array.from把伪数组对象转为数组
//调用函数
let result = context.fn(...args)
delete context.fn
return result
}
**apply**
Function.prototype.myApply=function(context=window){
    if(typeof this !=="function"){
       throw new TypeError("Not a Function")
       }
    let result;
    context.fn=this
    if(argument[1]){
       result=context.fn(...arguments)
       }
    else{
      result=context.fn()  
    }
    delete context.fn
    return result
}
**bind**
Function.prototype.myBind=function(context){
    //判断是否是一个函数
    if(typeof this !=="function"){
      throw new TypeError("Not a Function") 
       }
    //保存调用bind的函数
    const _this=this
    //保存参数
    const args=Array.prototype.slice.call(argument,1)
    //返回一个函数
    return function F(){
        //判断是不是new出来的
        if(this instanceof F){
           //如果是new出来的
            //返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
            return new _this(...args,...arguments)
           }else{
            //如果不是new出来的改变this指向,且完成函数柯里化
               return _this.apply(context,args.concat(...arguments))
        }
    }
}

14.函数柯里化

//函数柯里化就是我们给一个函数传入一部分参数,此时就会返回一个函数来接收剩余的参数。
//https://www.jb51.net/article/234665.htm
//未使用柯里化
function sum(a,b,c){
  return a+b+c
}
console.log(sum(1,2,3))//6
//使用柯里化
function sum(a){
    return function(b){
        return function(c){
            return a+b+c
        }
    }
}
console.log(sum(1,2,3))//6
//上面函数可以简写
const sum=a=>b=>c=>a+b+c
console.log(sum(1,2,3))//6
//实现原理
function curry(fn){
    return function currying(...args) {
          if(args.length>=fn.length){
      return fn.apply(this,args)
      }else{
        return function(...args2){
            return currying.apply(this,args.concat(args2))
        }
    }  
    }

}

15. for...in,for...of,for,forEach的区别与Object.keys

对象的数据属性有{
value:属性值,
writable:true/false,//控制是否可修改
enumerable:true/false,//控制是否可以被for in 遍历,可以就是**可枚举属性**,不可以就是不可枚举属性
configurable:true/false,//控制是否可删除,控制是否可修改前两个特性,一旦改为false就不可逆
get和set函数
}
for...in 更适合遍历对象,遍历的是索引Index(键名),会遍历手动添加的键名和原型上的属性和方法,**会遍历整个原型链**,可以实现浅拷贝
let tempObj={}
for(let key in obj){
tempObj[key]=obj[key]
}
Object.keys()用于获得由对象属性名组成的数组,可与数组遍历(forEach,for)相结合使用
let person={
    name:"张三",
    age:20,
    sex:"男"
}
Object.keys(person).forEach(e=>{
    console.log(e,"+",person[e]) // name+张三 age+20 sex+男
})
//Object.keys性能更优
for...of ES6新增,更适合遍历数组,只会遍历的键值,获取对象的value值,可以配合return,break中途跳出循环
for 可以控制循环起点,可以中途跳出,可以修改索引
forEach es5提出,循环无法中途跳出,无法对循环对象中的内容进行增删改操作,循环中不能修改索引(底层隐式控制index自增,无法操作)break 命令或 return 命令都不能奏效(借助try/catch可以跳出),不能循环伪数组(argument),没有返回值,默认return undefined
性能方面:for>forEach(有回调函数(v,i,arr)和上下文(this))>map(创建一个新数组,产生新的内存空间)
遍历 Symbol 类型的值需要用 Object.getOwnPropertySymbols() 方法