JavaScript——闭包和继承

一、闭包
  • 函数的两个阶段:

1、定义阶段

1)开辟一个存储空间

2)把函数体内的代码一模一样的放在这个空间内(不解析变量)

3)把存储空间的地址给函数名

2、调用阶段

1)按照函数名的地址找到函数的存储空间

2)形参赋值

3)预解析

4)在内存中开辟一个执行空间

5)将函数存储空间中的代码拿出来在刚刚开辟的执行空间中执行

6)执行完毕后,内存中开辟的执行空间销毁

  • 函数的执行空间:

每一个函数会有一个存储空间

但是每一次调用都会生成一个完全不一样的执行空间

并且执行空间会在函数执行完毕后就销毁了,但是 存储空间 不会

我们可以有一些办法让这个空间 不销毁

闭包,就是要利用这个不销毁的执行空间

  1. 闭包介绍

函数作用域嵌套,造成变量执行完不被销毁的场景就叫闭包。闭包不是新的语法,是函数嵌套后产生一种神奇的场景。函数内嵌套函数,并返回内函数的引用地址,在外部定义变量来接收。

总结:局部的引用类型数据跟全局的变量,产生了引用的关系。全局变量一直能被使用,局部的引用类型就不能被销毁,局部的执行空间一直存在内存中,没有被销毁。

  1. 闭包的优缺点

优点:

1)保护私有变量不被全局污染

2)间接的让函数外可以访问函数内的变量

3)延迟了变量的生命周期

缺点:

外面函数每调用一次,就会在调用栈保留一个执行空间,调用多了话,可能会造成内存溢出/内存泄漏

  1. 闭包的应用

(1)在循环中绑定事件,事件函数中需要使用循环的变量

(2)在循环中执行异步代码,在异步代码中使用循环的变量

(3)防抖:当某些事件在触发的时候,行为执行了一点,就会触发多次事件(鼠标移动事件、键盘按下事件、浏览器滚动事件、浏览器大小改变事件、文本框内容即时改变等)的时候,很多情况下,我们都只是需要最后一次触发的结果,中间触发的多次事件都是多余的、浪费的,此时就需要防抖。

防抖:简单地说,就是 当一个事件连续触发,只执行最后一次。

  • 一般使用场景:

登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖

调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖

//1.封装防抖函数
function debounce(fn, time) {
    //4.创建一个标记用来存放定时器的返回值
    let timer = null
    return function () {
        //5.每当用户触发input事件,把前一个setTimeout清除掉
        clearTimeout(timer)
        //6.又创建一个新的setTimeout,这样就能保证输入字符后等待的间隔内,还有字符输入的话,就不会执行setTimeout
        timer = setTimeout(() => {
            //7.这里进行防抖的内容
            fn()
        }, time)
    }
}
//8.测试防抖临时使用函数
function sayHi() {
    console.log("防抖成功")
}
//2.获取操作元素
var inp = document.querySelector("input")
//3.给inp绑定input事件,调用封装的防抖函数,传入要执行的内容与间隔时间
inp.addEventListener('input', debounce(sayHi, 5000))

(4)节流:一个事件会在很短的时间内触发多次的时候,其中很多事件在触发后,我们无法把握其中的每个事件,所以就造成了多余和浪费,我们可以让事件在一段我们能把握到、反应过来的时间段执行一次,其实是减少触发率。

节流:简单地说,就是限制一个事件在一段时间内只能执行一次

  • 一般使用场景:

scroll 滚动事件,每隔一秒计算一次位置信息等

input 框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求

//使用定时器和开关节流
//1.封装节流函数
function throttle(fn, time) {
    //3.通过闭包保存一个“节流阀” 默认为true
    let flag = true
    return function () {
        //8.触发事件被调用,判断节流阀是否为false。如果为false就直接return出去不做任何操作
        if (!flag) {
            return
        }
        //4.如果节流阀为true,立即将节流阀设置为false
        flag = false
        //5.开启定时器
        setTimeout(() => {
            //6.将外部传入的函数的执行放在setTimeout中
            fn.apply(this, arguments)
            //7.最后在setTimeout执行完毕后再把标记节流阀为true,表示可以进行下次循环了
            flag = true
        }, time)
    }
}
//8.测试防抖临时使用函数
function sayHi(e) {
    //打印当前document的宽高
    console.log(e.target.innerWidth, e.target.innerHeight)
}
//2.绑定事件,绑定时就调用节流函数
//绑定事件要调用一下封装的节流函数,触发事件时触发封装函数内部的函数
window.addEventListener('resize', throttle(sayHi, 2000))
//利用时间差节流
document.onmousemove = throttling(fn, 1000)
function throttling(handler, time) {
    var startTime = +new Date()
    return function() {
        var now = +new Date()
        if(now - startTime >= time) {
            handler.call(this, ...arguments)
            startTime = now
        }
    }
}
function fn(e) {
    console.log( e.pageX );
}

总结:防抖是控制次数,节流是控制频率。

(5)函数柯里化:如果一个函数调用传递了多个实参,函数定义就需要多个形参来接收。函数柯里化,就是让函数不接收全部的实参,只接收部分参数,然后在函数内再次返回一个小函数,来接收剩余部分的参数,让函数整个运行流程,可以分多个步骤执行。

function add(a){
    return function(b){
        console.log(a+b)
    }
}
add(2)(3)
二、继承
  1. 继承介绍

继承是让一个对象可以拥有另一个对象的属性和方法,而不用自己去添加,类似于原型和实例对象的关系。面向对象编程,有一个特性就是继承。

  1. 原型继承

我们可以通过修改对象的原型,让对象能拥有其他对象的属性和方法。

弊端:继承来的属性在原型上,不在自己上,当给自己添加同名属性时,就无法使用原型的属性了。

优点:可以继承父类构造函数和原型空间中的所有内容

缺点:不能给父类构造函数中传递参数

function animal(){
    this.name="动物"
}
animal.prototype.sport=function(){
    console.log("运动")
}
let a=new animal()
console.log(a)

function Birds(){
    this.wing="翅膀"
}
// 为了能让bird实例对象能拥有animal对象的属性和方法 - 将animal对象作为bird实例对象的原型
Birds.prototype = a

let b=new Birds()
console.log(b)

Birds.prototype.fly=function(){
    console.log("飞翔")
}
console.log(b.name)  //动物
b.sport()            //运动
//不能给父类构造函数中传递参数
  1. 借用函数继承

通过借用函数,可以在子构造函数中,执行父构造函数中的代码,将父构造函数中的属性添加在子构造函数中。

优点:可以继承父类构造函数中的属性和方法,也可以给父类构造函数中传递参数

缺点:无法继承父类原型空间中的内容

function animal() {
    this.name = "动物"
}
animal.prototype.sport = function () {
    console.log("运动")
}
let a = new animal()
console.log(a)

function Birds() {
    //借用函数继承
    //在这里执行父构造函数中的代码并将其中的this改成子构造函数中的this
    animal.call(this)
    this.wing = "翅膀"
}

let b = new Birds()
console.log(b)
Birds.prototype.fly = function () {
    console.log("飞翔")
}
console.log(b.name)  //动物
b.sport()            //报错
//无法继承父类原型空间中的内容
  1. 组合继承

组合继承:为了解决原型继承和借用函数继承的弊端,可以将这两种继承方式都使用上。

优点:既可以继承父类构造函数中的内容,又可以继承父类原型空间中的内容,还可以给父类构造函数中传递参数

function animal() {
    this.name = "动物"
}
animal.prototype.sport = function () {
    console.log("运动")
}
let a = new animal()
console.log(a)

function Birds() {
    //借用函数继承
    // 在这里执行父构造函数中的代码并将其中的this改成子构造函数中的this
    animal.call(this)
    this.wing = "翅膀"
}

//原型继承
Birds.prototype = a
let b = new Birds()
console.log(b)
Birds.prototype.fly = function () {
    console.log("飞翔")
}
console.log(b.name)  //动物
b.sport()            //运动
  1. ES6的类

类是抽象的对象,对象是实例化的类

用class定义一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实例对象可以共享的。(除静态属性和静态方法以外)

//定义类
class 类的名字{}

//类只有一个作用,就是定义对象,定义对象
var 对象 = new 类

//给对象中添加属性,就要在类中定义
class 类的名字{
    // 方法1
    属性名 = 值 
    // 方法2
    constructor() {
        this.属性名 = 值
    }
    //给对象添加方法
    方法名(){
        代码段
    }
}
class Animal{
    name = '动物'
    constructor(age) {
        this.age = age
        this.say = '叫'
    }

    sport() {
        console.log('运动');
    }
}
var a = new Animal(5)
console.log(a)
  1. ES6的继承

class之间可以通过extends关键字实现继承,这比ES5通过修改原型链实现继承,要清晰和方便很多。

在ES6的类中定义的对象,实现继承有继承的语法:

class 子类 extends 父类{ }
//跟组合继承的效果是一样的

super关键字,它指向父类的实例(即父类的this对象)。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。

这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

class Animal{
    name = '动物'
    sport() {
        console.log('运动');
    }
}

class Bird extends Animal{
    constructor(age) {
        //如果父类中有constructor,super就相当于在调用父类的constructor
        super()
        this.age = age
    }
}

var b = new Bird(12)
console.log(b);

ES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

同一个父类,可以被多个子类继承,但每个子类都可以在同样的属性和方法中有不同的表示显示。用专业术语来讲,叫做多态。

注意:

1、当子类继承父类时,子类中什么内容都没有时,创建的子类实例对象,实际上表示父类实例对象

2、当父类和子类中都没有constructor方法时,可以不使用super来进行修改this执行,同样可以继承父类中的属性和方法

3、当父类和子类中都有constructor方法时,那么需要在子类的constructor方法的第一行使用super方法来修改this指向

4、当父类中的属性跟子类中的属性同名时,子类属性会覆盖父类属性或方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值