闭包(closure),继承(extends),立即执行函数(IIFE)

933 阅读5分钟

闭包

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

听了一些参加工作的学长说,闭包(closure)是面试中的一个考点,所以我想通过来写一篇关于闭包的东西,来加深这方面的理解。

闭包,首先涉及的是全局变量和局部变量这两个东西,在默认情况下,全局变量不能访问局部的变量,但是局部的变量能够访问全局的变量。

例如:

function fn(){
    var a = 10;
    function fun(){
        var b = 20;
        console.log('i am fun函数')
    }
    console.log('i am fn函数')
    fun() //fun函数只有在这里调用才有效
}
fn()

以下是闭包(colsure)相关的重要内容:

1.函数在创建的时候会创建两个对象:一个是函数本身,另一个是作用域链对象。

2.函数在调用的时候会创建一个执行环境对象(活动对象),并且活动对象里面放的是当前活动对象里的调用变量。

全局变量与局部变量各有什么缺点?

1.在函数体外定义,每个函数都能修改,会造成全局污染。

2.在函数体内定义,只有当前的函数才能使用,但是不能在全局重复使用。

所以,要想既能重复的使用,又不会造成污染,使用闭包!!!

经典的闭包包括三个步骤:

1.外层函数嵌套内层函数

2.内层函数使用外层函数的局部变量

3.把内层函数作为外层函数的返回值

为什么闭包既能重复使用,又不会污染全局?

因为如果是普通的调用函数后,这个被调用的函数就呗释放了,但是闭包后,会有一个定义的变量把他们都挂起来,让他们都释放不了,因此,闭包也存在一个缺点,占内存!!!

下面请看图:

在上图中,我创建了一个经典的闭包函数,并且画了一个示意图,函数在执行的过程中,会把函数的部分存在栈中,用一个地址把它存起来,所以,我们在调用时,是调用它的地址,因为函数在创建的过程中不仅会创建函数本身,也会创建一个作用域链对象,并进入栈中,当调用后,它就会被释放掉,防止占用内存,但是在闭包函数中,外层函数的作用域链对象指向内层函数的作用域对象,而内层函数的作用域链对象又指向内层函数调用的活动对象,而这个活动对象又是作为外层函数的返回值,此时,外层函数的作用域链对象又被在外建立的fn函数挂了起来,所以就形成了一个闭包的环境。使得它们不会被释放掉。以达到可以重复使用,但又不会污染全局的作用。

下面是我用闭包写的一个例子,通过点击每个li得到它们的下标。

for(var i = ;i < list.length;i++){
    list[i].onclick = (function(index){
        return function(){
            console.log(index)
        }
 })(i)
}

继承

子类共享父类的属性和方法,js的继承都是基于原型实现的,此外,在说继承之前,还要再复习一个概念,多态:重写,重载。

在继承这方面,我只想写关于ES6的继承,俗称:语法糖。

class Animal{
    constructor(name){
        this.name = name;
    }
    say(){
        alert("my name is " + this.name);
    }
    eat(food){
        alert()this.name +"is eating" +food);
    }
}
//下面是继承的代码,重点:
calss cat extends Animal{
    constructor(name){
        super(name);
    }
}
var tom = new cat("tom");
tom.say();
tom.eat("apple");

console.log(cat instanceof Animal);//true
console.log(cat instanceof Cat);//true

补充一点:instanceof 是用来检测一个对象是否是一个对象的实例。

案例:(拖拽的实现)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    .box {
      width: 200px;
      height: 200px;
      background-color: red;
      position: absolute;
    }
  </style>
</head>
<body>
  <div class="box" id="box1">box1</div>
  <div class="box" id="box2">box2</div>

下面是javascript代码:

 <script>
    class Drag {
      constructor (selector) {
        this.box = document.querySelector(selector)
        this.bindEvents()
      }
      bindEvents () {
        this.box.onmousedown = (e) => {
          let disX = e.offsetX;
          let disY = e.offsetY;
          document.onmousemove = e => {
            let top = e.clientX - disX;
            let left = e.clientY - disY;
            this.move(left, top)
          }
          document.onmouseup = function () {
            document.onmouseover = null
          }
          // 阻止默认行为来防止拖动文字
          e.preventDefault()
        }
      }
      move (left, top) {
        this.box.style.left = left + 'px'
        this.box.style.top = top + 'px'
      }
    }

    class LimitDrag extends Drag {
      constructor (selector) {
        super(selector)
      }
      move (left, top) {
        // 重写move
        if (left < 0) left = 0
        if (top < 0) top = 0
        // TODO: 限定右下角
        this.box.style.left = left + 'px'
        this.box.style.top = top + 'px'
      }
    }
    
    new Drag('#box1')
    new LimitDrag('#box2')
  </script>

class LimitDrag extends Drag 其实当写完这一句代码的时候,就已经继承了父类的东西。

立即执行函数

立即执行函数的两种常见形式:

( function(){…} )()

( function (){…} () )

一个是一个匿名函数包裹在一个括号运算符中,后面再跟一个小括号,另一个是一个匿名函数后面跟一个小括号,然后整个包裹在一个括号运算符中,这两种写法是等价的。要想立即执行函数能做到立即执行,要注意两点,一是函数体后面要有小括号(),二是函数体必须是函数表达式而不能是函数声明。

从图中可以看出,除了使用()运算符之外,!,+,-,=等运算符都能起到立即执行的作用。这些运算符的作用就是将匿名函数或函数声明转换为函数表达式,如下图所示,函数体是函数声明的形式,使用运算符将其转换为函数表达式之后就可达到立即执行效果:

2.使用立即执行函数的好处

通过定义一个匿名函数,创建了一个新的函数作用域,相当于创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏污染全局的命名空间。此时若是想访问全局对象,将全局对象以参数形式传进去即可