知识点:
- 执行上下文
- 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