闭包和作用域

1、执行上下文

  在一段 JS 脚本(即一个`<script>`标签中)执行之前,会先创建一个**全局执行上下文**环境,先把代码中即将执行的(内部函数的不算,因为不知道函数何时执行)变量、函数声明(和“函数表达式”的区别)都拿出来。变量先暂时赋值为`undefined`,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。这是在代码执行之前才开始的工作

  另外,一个函数在执行之前,也会创建一个**函数执行上下文**环境,跟**全局上下文**差不多,不过**函数执行上下文**中会多出`this` `arguments`和函数的参数。

总结一下

- 范围:一段`<script>`或者一个函数
- 全局:变量定义,函数声明
- 函数:变量定义,函数声明,this,arguments

#参考实例
console.log(a) // undefined var a = 100 fn('zhangsan') // 'zhangsan' 20 function fn(name) { age = 20 console.log(name, age) var age }

 2、 this

定义:`this`的值是在执行的时候才能确认,定义的时候不能确认!

因为`this`是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候。

var a = {
  name: 'A',
  fn: function () {
  console.log(this.name)
}
} //this作为对象属性执行
a.fn() // this === a
a.fn.call({name: 'B'}) // this === {name: 'B'}
var fn1 = a.fn
fn1() // this === window

`this`执行会有不同,主要集中在这几个场景中

- 作为构造函数执行
- 作为对象属性执行
- 作为普通函数执行
- 用于`call` `apply` `bind`

//作为构造函数执行,Foo是构造函数
function Foo (name) {
    this.name = name
    console.log(this.name)
}
var fn1 = new Foo("zhangsan")
fn1();    ----this指向fn1

//作为一个对象属性
var a = {
    name: 'A',
    fn: function () {
        console.log(this.name)
    }
}
a.fn();   ---this指向 a

//作为普通函数执行,函数inner独立调用(不论这个函数在哪调用),this默认指向到window
function outer(){
     function inner(){
        console.log(this === window);
     }
     inner();
}
outer();   ---this指向window

//call、apply、bind调用:三者的第一个参数都是this要指向的对象;
//bind 只是返回函数,还未调用,所以如果要执行还得在后面加个();call、apply 是立即执行函数;
func.call(obj,value1,value2);     //call 后面的参数用逗号隔开
func.apply(obj,[value1,value2]); //apply 后面的参数以数组的形式传入
func.bind(obj,value1,value2)();  
func.bind(obj)(value1,value2);  //bind则可以在指定对象的时候传参,和 call 一样,以逗号隔开,也可以在执行的时候传参,写到后面的括号中

 3、作用域

  JS 没有块级作用域,只有全局作用域和函数作用域。

   作用域就是一个独立的地盘,让变量不会外泄、暴露出去。下面的`name`就被暴露出去了,因此,JS 没有块级作用域

  if (true) {
    var name = 'zhangsan'
  }
  console.log(name)

  全局作用域就是最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么他们就全部都在全局作用域中。这样的坏处就是很容易装车。

  函数作用域是放在`(function(){....})()`中的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。

  作用域链是向父级作用域寻找,一层一层向上寻找,直到找到全局作用域还是没找到当前的自由变量,就宣布放弃的这种一层一层的关系。

var a = 100
function F1() {
    var b = 200
    function F2() {
        var c = 300
        console.log(a)
        console.log(b)    //‘a','b'都为【自由变量】
        console.log(c)
    }
    F2()
}
F1()

  4、闭包

  自由变量将从作用域链中去寻找,但是【依据的是函数定义时的作用域链,而不是函数执行时】,这就是闭包

  闭包主要有两个应用场景:

  - 函数作为返回值
  - 函数作为参数传递

#函数作为返回值
function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
var f1 = F1()
var a = 200
f1()


#函数作为参数传递
function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
function F2(f1) {
    var a = 200
    console.log(f1())
}
var f1 = F1()
F2(f1)

5、实际开发中闭包的使用场景

  闭包的实际应用,主要是用来封装变量。即把变量隐藏起来,不让外面拿到和修改。

  在 isFirstLoad 函数外面,根本不可能修改掉 _list 的值。

function isFirstLoad() {
    var _list = []

    return function (id) {
        if (_list.indexOf(id) >= 0) {
            return false
        } else {
            _list.push(id)
            return true
        }
    }
}

// 使用
var firstLoad = isFirstLoad()
firstLoad(10) // true
firstLoad(10) // false
firstLoad(20) // true

6、创建 10 个`<a>`标签,点击的时候弹出来对应的序号

#错误的写法   【监听的i是全局作用域的变量:“自由变量“,因此每次点击都是10】
var i, a
for (i = 0; i < 10; i++) {
    a = document.createElement('a')
    a.innerHTML = i + '<br>'
    a.addEventListener('click', function (e) {
        e.preventDefault()
        alert(i)    //自由变量,要去父作用域获取值
    })
    document.body.appendChild(a)
}

#正确的写法【每个i都生成一个函数var i
for (i = 0; i < 10; i++) {
    (function (i) {
     //生成了一个函数作用域
var a = document.createElement('a') a.innerHTML = i + '<br>' a.addEventListener('click', function (e) { e.preventDefault() alert(i) }) document.body.appendChild(a) })(i) }

 

posted @ 2020-11-19 23:36  风露  阅读(119)  评论(0编辑  收藏  举报