JavrScript 闭包和继承

本文详细介绍了JavaScript中的闭包和继承。闭包重点讨论了其形成原理、好处及应用,包括在循环中处理异步操作、防抖、节流和函数柯里化。而继承部分则涵盖概念、原型继承、借用函数继承、组合继承,以及ES6中的类和继承,探讨了多态等面向对象编程特性。

闭包和设计模式

一、闭包

1、闭包介绍

是由于作用域嵌套,让全局变量和局部的引用类型数据保持了引用关系,导致执行空间不销毁的场景。

例:

// 普通的函数没有嵌套
function fn() {
    var a = 1
    console.log(a++);
}
fn() // 1
fn() // 1
fn() // 1

// 有函数嵌套
function big() {
    var a = 1
    function small() {
        console.log(a++);
    }
    return small
}
var s = big()
s() // 1
s() // 2
s() // 3

函数作用域嵌套,造成变量执行完不被销毁的场景就叫闭包。

2、闭包形成的原理

js代码执行,都在内存的调用栈内存中进行,函数调用后,会在调用栈中先创建一个执行空间,局部变量,会在执行空间中创建,然后执行代码,当函数代码执行接收后,会从调用栈中销毁这个执行空间。

所以,没有函数嵌套的时候,函数中的局部变量,在执行结束后,执行空间销毁,变量也会被销毁,下次调用的时候会重新在调用栈创建执行空间。

如下图:

在这里插入图片描述

这个过程重复了3次。所以每次输出都是1。

闭包的执行过程如下图:

在这里插入图片描述
全局s要和执行空间1中的small保持引用关系,全局s要在后面也能使用,所以执行空间1中的small不能被销毁,所以执行空间1也不能销毁。

所以第二次调用的时候,输出的还是执行空间1中的变量a,是第一次修改后的值2。

通常表现:

  • 大函数中返回小函数
  • 小函数使用大函数中的变量
  • 全局变量跟局部的小函数保持引用关系

3、闭包的好处

  1. 保护私有变量不被全局污染
  2. 间接的让函数外可以访问函数内的变量
  3. 延长了变量的生命周期

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

4、闭包的应用

4.1、循环中使用异步或事件
<ul>
    <li>javascript修炼内功</li>
    <li>vue修炼独孤九剑</li>
    <li>react修炼乾坤大挪移</li>
</ul>
</body>
<script>
var lis = document.querySelectorAll('li')
for(var a=0; a<lis.length; a++) {
    lis[a].onclick = click(a)
}
function click(a) {
    return function() {
        alert(lis[a].innerText)
    }
}
</script>
4.2、防抖

有一些事件,触发的频率特别高,往往我们在网页中动了一下,事件就会触发很多次,例如:鼠标移动事件、键盘按下事件、浏览器滚动事件、浏览器大小改变事件、文本框及时改变内容事件等。或者人为的频繁触发事件,例如:滑动轮播图,在第一张图还没有完全滑动过去,就点击了多次按钮来触发滑动行为。

这些频繁触发的事件,往往我们不需要触发太多次数,多个动作中,只需要触发一次或几次,而不是每次都触发。

防抖:指事件在频繁的触发时,函数只执行一次。

获取最后一次执行的结果:

document.onmousemove = debounce(fn, 1000)
function debounce(handler, time) {
    var timer = 0
    return function() {
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.call(this, ...arguments)
        }, time)
    }
}
function fn(e) {
    console.log( e.pageX );
}

获取第一次执行的结果:

document.onmousemove = throttling(fn, 1000)
function throttling(handler, time) {
    var flag = true
    var timer = 0
    return function() {
        if(flag) {
            handler.call(this, ...arguments)
            flag = false
        } else {
            clearTimeout(timer)
            timer = setTimeout(() => {
                flag = true
            }, time)
        }
    }
}
function fn(e) {
    console.log( e.pageX );
}
4.3、节流

节流:事件在频繁触发时,不是每次触发都会执行函数,让函数有节制的执行。也就是让函数在一个时间段内虽然触发了多次,但是函数只执行了一次。

利用时间差节流:

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 );
}

使用定时器和开关节流:

function throttling(handler, time) {
    var flag = true
    return function() {
        if(!flag) return
        flag = false
        setTimeout(() => {
            handler.call(this, ...arguments)
            flag = true
        }, time)
    }
}
4.4、函数柯里化(拆分形参)

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

正常函数:

function add(a, b) {
	console.log(a + b)
}

add(1, 2) // 3

柯里化后的函数:

function add(a) {
    return function(b) {
        console.log(a + b)
    }
}
var fn = add(1)
fn(2) // 3
// 简写成
add(2)(3) // 5

二、继承

1、继承的概念

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

面向对象编程,有一个特性就是继承。

2、原型继承

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

function Animal() {
    this.name = '动物'
}

Animal.prototype.sport = function() {
    console.log('运动');
}

var animal = new Animal()
console.log(animal);

function Bird() {
    this.wing = '翅膀'
}

// 为了能让bird实例对象能拥有animal对象的属性和方法 - 将animal对象作为bird实例对象的原型
Bird.prototype = animal

Bird.prototype.fly = function() {
    console.log('飞翔');
}

var bird = new Bird()
console.log(bird);
console.log(bird.name);
bird.sport()

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

3、借用函数继承

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

function Animal() {
    this.name = '动物'
}

Animal.prototype.sport = function() {
    console.log('运动');
}

var animal = new Animal()
console.log(animal);

function Bird() {
    // 在这里执行父构造函数中的代码并将其中的this改成子构造函数中的this
    Animal.call(this)
    this.wing = '翅膀'
}

Bird.prototype.fly = function() {
    console.log('飞翔');
}

var bird = new Bird()
console.log(bird);
console.log(bird.name);
bird.sport()

弊端:借用函数继承,只能继承属性,无法继承原型中的方法

4、组合继承

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

function Animal() {
    this.name = '动物'
}

Animal.prototype.sport = function() {
    console.log('运动');
}

var animal = new Animal()
console.log(animal);

function Bird() {
    // 借用函数继承
    Animal.call(this)
    this.wing = '翅膀'
}

// 原型继承
Bird.prototype = animal

Bird.prototype.fly = function() {
    console.log('飞翔');
}

var bird = new Bird()
console.log(bird);
console.log(bird.name);
bird.sport()

5、ES6的类

高级编程语言,都有面向对象编程,面向对象编程,要有专门的创建方式,在以往的js版本中,使用构造函数来创建。函数本身具有自己让代码复用的功能,再加上创建对象外,反而让js语言显的比较混乱,所以es6增加了专门用于定义对象的数据:类。

类是抽象的对象,对象是实例化的类。所有对象都是由类衍生的,例如人,具体的说,张三这个人,是从人类中衍生的,将人类具体化以后得到的。人类是众多具体的人抽象画后的概念。

ES6中定义类的语法:

class 类的名字{}

类只有一个作用,就是定义对象,定义对象的语法:

var 对象 = new

类的本质,其实还是一个函数,只是换了一种形式,这种形式只能用来定义类,而不能当做构造函数调用了。

class Animal{
    name = '动物'
}
console.log(Animal);
console.log(typeof Animal); // function
Animal() // 报错

此时对象是空对象,要给对象中添加属性,就要在类中定义:

class 类的名字{
    属性名 =// 方法1
	// 方法2
    constructor() {
        this.属性名 =}
}

在new类的时候,传递的实参,其实是传递给了constructor,contructor方法只会在new类的时候执行一次。

给对象添加方法:

class 类的名字{
    方法名() {
        代码段
    }
}

类中写好的方法,默认会出现在对象的原型中。

例:

class Animal{
    name = '动物'
    constructor(age) {
        this.age = age
        this.say = '叫'
    }

    sport() {
        console.log('运动');
    }
}
var a = new Animal(5)
console.log(a)

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Czckf396-1681538836052)(D:/课上练习/二阶段/预习资料/media/1667669565276.png)]

例:利用es6的类写tab切换和轮播图。

6、ES6的继承

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

class 子类 extends 父类{}

例:

class Animal{
    name = '动物'
    constructor(age) {
        this.age = age
        this.say = '叫'
    }

    sport() {
        console.log('运动');
    }
}

class Bird extends Animal{}

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

输出结果:

在这里插入图片描述

跟组合继承的效果是一样的。

如果子类中有constructor方法的话,必须在constructor中的第一行代码调用super方法,否则会报错。

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

class Bird extends Animal{
    constructor(age) {
        this.age = age
    }
    
}

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

报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ZwqD0LD-1681538836053)(D:/课上练习/二阶段/预习资料/media/1667669810850.png)]

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

class Bird extends Animal{
    constructor(age) {
        super()
        this.age = age
    }
    
}

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

如果父类中有constructor,super就相当于在调用父类的constructor,例:

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

class Bird extends Animal{
    constructor(age, say) {
        super(say)
        this.age = age
    }
    
}

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

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lKAum7eC-1681538836054)(D:/课上练习/二阶段/预习资料/media/1667669939907.png)]

继承自父类的方法和属性,子类是可以重写的(覆盖):

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

class Bird extends Animal{
    constructor(age, say) {
        super(say)
        this.age = age
        this.name = '鸟'
    }
    sport() {
        console.log('飞翔');
    }
}

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

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YbCHuGCB-1681538836055)(D:/课上练习/二阶段/预习资料/media/1667670026346.png)]

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

继承自父类的方法和属性,子类是可以重写的(覆盖):

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

class Bird extends Animal{
    constructor(age, say) {
        super(say)
        this.age = age
        this.name = '鸟'
    }
    sport() {
        console.log('飞翔');
    }
}

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

结果:

[外链图片转存中...(img-YbCHuGCB-1681538836055)]

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值