作用域和闭包

知识点:

  • 执行上下文
  • this
  • 作用域
  • 作用域链
  • 闭包

执行上下文

  • 范围:一段<script>或者一个函数
  • 全局:变量定义,函数声明 【一段<script>】
  • 函数:变量定义,函数声明,this,arguments(函数中参数的集合)【函数】

ps. 注意“函数声明”和“函数表达式”的区别

console.log(a)  // undefined
var a = 100   // 函数表达式

fn('zhangsan')  // 'zhangsan' 20
function fn(name) { 
    // 函数

    console.log(this) // Window{}
    console.log(arguments)  // ["zhangsan",...]
  
    age = 20
    console.log(name, age)
    var age

    bar(100)
    function bar(num) {
        console.log(num)
    }
}               // 函数声明

拿到程序之后会进行解析,在执行代码之前做了几件事:(创建一个全局执行上下文环境)

  • 把变量 a 拿出来,并用 undefined 赋值(占位)
  • 把函数声明 fn 整体拿出来(可执行状态)

函数 fn 在执行之前:(创建一个函数执行上下文环境)

  • 先把变量 age 拿出来,用 undefined 来代替(所以 age = 20 没有问题)【与上同】
  • 把函数声明 bar 整体拿出来 【与上同】
  • fn 中的 this 也要提前拿出来【记住是执行之前,不是声明之前,不是定义之前】
  • fn 中的 arguments(参数,注意带复数 s,如 name)也要提前拿出来

实际写代码时还是要该怎么写就怎么写,先定义后执行!不推荐这种写法!

fn()   // 正常执行
function fn() {
    // 声明
}

fn1()  // undefined
var fn1 = function () {
    // 表达式
} 

this

this 要在执行时才能确认值,定义时无法确认。

var a = {
    name: 'A',
    fn: function () {
        console.log(this.name)
    }
}
a.fn()  // this === a
a.fn.call({name: 'B'})  // this === {name: 'B'}
var fn1 = a.fn
fn1()  // this === window

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

  • 作为构造函数执行
  • 作为对象属性执行
  • 作为普通函数执行
  • 用于 call apply bind
// 作为构造函数执行
function Foo(name){
    // this = {}    this 先声明成一个空对象
    this.name = name
    // return this  最后再返回 this
}
var f = new Foo('zhangsan')

// 作为对象属性执行
var obj = {
    name:'A',
    printName:function(){
        console.log(this.name)
    }
}
obj.printName()  // 作为对象属性来执行,this 指代的就是这个对象 obj

// 作为普通函数执行
function fn(){
    console.log(this)  // this === window
}
fn()

// 用于 call apply bind(call 最常用)
function fn1(name, age){
    alert(name)
    console.log(this)   // 执行以后输出 Object {x:100}
}
fn1.call({x:100}, 'zhangsan', 20)  // this 指代 {x:100}
fn1.apply({x:100}, ['zhangsan', 20])  // this 指代 {x:100}

var fn2 = function(name, age){  // .bind() 是函数表达式的方式,故不能用函数声明 function(){}.bind(),会报错
    alert(name)
    console.log(this)   // 如没有 bind,this === window
}.bind({y:200})        // 函数在声明的时候就规定了 this 指代 Object {y:200}
fn2('zhangsan', 20)

作用域

  • 没有块级作用域
  • 只有函数和全局作用域
// 无块级作用域
if (true) {
    var name = 'zhangsan'
}
console.log(name)

// 如果是 C/Java 等语言,代码会报错
// js 不会报错而且还会输出 name 的值
// 所以写代码时要避免在块内声明变量,提高程序易读性
// 有函数和全局作用域
var a = 100
function fn() {
    var a = 200
    console.log('fn', a)
}
console.log('global', a)
fn()

// 全局作用域易被改写,不安全
// 函数作用域里的变量不会被污染

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

// 张三写的代码中
var data = {a:100}

// 李四写的代码中
var data = {x:true}

这就是为何 jquery zepto 等库的源码,所有的代码都会放在(function(){....})()中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。

作用域链

var a = 100
function fn() {
    var b = 200

    // 定义:当前作用域没有定义的变量,即“自由变量”
    // 去函数的父级作用域(本例中:全局作用域)去找 a
    // 函数的父级作用域是函数定义(not 函数执行)时的父级作用域
    console.log(a)

    console.log(b)
}
fn()

自由变量如何得到 —— 向父级作用域寻找。如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链

var a = 100
function F1() {
    var b = 200
    function F2() {
        var c = 300
        console.log(a)  // a 是自由变量
        console.log(b)  // b 是自由变量
        console.log(c)
    }
    F2()
}
F1()

闭包

function F1() {
    var a = 100

    // 返回一个函数(函数作为返回值)
    return function () {
        console.log(a)    // 自由变量,父作用域寻找
    }
}

// f1 得到一个函数
var f1 = F1()
var a = 200   // 这里的 a 和上边的 a 是两个概念
f1()          // 返回值为 100

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

闭包的使用场景:

  • 函数作为返回值(如上)
  • 函数作为参数传递(如下)
function F1() {
    var a = 100
    return function () {
        console.log(a)    // 自由变量,父作用域寻找,与执行时的作用域无关
    }
}
function F2(fn) {
    var a = 200
    fn()
}
var f1 = F1()
F2(f1)        // 返回值仍为 100

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值