题目:
- 说一下对变量提升的理解
- 说明this几种不同的使用场景
- 创建10个<a>标签,点击的时候弹出来对应的序号
- 如何理解作用域
- 实际开发中闭包的应用
知识点:
1、执行上下文
2、this
3、作用域
4、作用域链
5、闭包
一、执行上下文
- 范围:一段script内或一个函数
- 全局:变量定义、函数声明 ——范围在一段<script>标签内。在函数执行之前,先把变量定义、函数声明拿出来
- 函数:变量定义、函数声明、this、arguments——范围在函数内。在函数执行之前,先把函数中的变量定义、函数声明、this、arguments拿出来
PS:函数声明和函数表达式的区别:
//执行上下文
console.log(a) //undefined
var a = 10
fn('张三',22) //张三 22
function fn(name){
age = 20
console.log(name,age)
var age
}
在script标签内的全局函数执行之前,如果是函数表达式,会把声明的变量名拿出来放到最前面,先赋值为undefined,如果是函数声明则把整个函数放到最前面。同理,在执行第5行的函数时,会先在函数内执行前,先把定义的变量、函数声明、this和arguments拿出来放到最前面,先赋值为undefined,然后再执行
二、this
- this要在执行时才能确认值,定义时无法确认
- 作为构造函数执行
- 作为对象属性执行
- 作为普通函数执行
- call apply bind
//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
function Foo(name) {
//首先创建一个this的空对象
this.name = name
// return this
}
var f = new Foo('zhangsan')
//对象属性中的this
var obj = {
name:'A',
printName:function () {
console.log(this.name)
}
}
obj.printName() //this指向obj
//作为普通函数来执行
function fn () {
console.log(this)
}
fn()//this === window
//call apply bind
function fn1(name, age) {
alert(name + age)
console.log(this)
}
// fn1("zhangsan")
fn1.call({x: 100}, "lisi", 22)
/*
第一个参数是指定this的指向,这里this指向{x:100}这个对象
第二个参数是传入的参数,第二个当参数
也就是说执行fn1时使用call,那么第一个参数就是this,就是这么简单!
在实际中call更常用
*/
// apply 除了传入参数是以一个数组形式外,和call一模一样
function fn2(name, sex) {
alert(name + "," + sex)
console.log(this)
}
fn2.apply({age:22},['taiyang','man'])
//bind,不能使用函数声明,使用bind必须是函数表达式
var fn3 = function fn3(name, age) {
alert(name + age)
console.log(this)
}.bind({
x: 123
})
fn3('taiyang', 22)
三、作用域
- JS没有块级作用域
- 只有函数和全局作用域
- 函数的父级作用域,是函数定义的时候的父级作用域,不是函数执行的时候的父级作用域,即函数在哪定义,父级作用域就在什么地方
//无块级作用域
if(true){
var name = 'taiyang'
}
console.log(name)
// //函数和全局作用域
var a = 100
function fn() {
var a = 200
console.log('fn',a)
}
fn()
console.log('global',a)
// 作用域链
var a = 200
//
// 这个函数在全局定义,其父级作用域就是全局作用域
function fn() {
var b = 100
// 当前作用域没有定义的变量,称作自由变量
console.log(a)
console.log(b)
}
fn()
// 父级作用域
var a = 100
// F1函数在全局定义,其父级作用域就是全局作用域
function F1() {
var b = 200
// F2函数在F1定义,其父级作用域就是F1的作用域
function F2(){
var c = 300
// a,b为自由变量,即不在该函数内定义的变量,首先会从F2函数中寻找a
//如果没有找到,会去寻找它的父级作用域F1,如果还没有找到,就继续寻找全局作用域,如果还没找到,就会报错
console.log(a)
console.log(b)
console.log(c)
}
F2()
}
F1()
理解父级作用域:一个函数的作用域是它定义时候的作用域,如这里F2的父级作用域就是:哪个函数定义了F2函数,那它的父级作用域就是那个函数,这对理解闭包是很重要的
四、闭包
闭包的使用场景
- 函数作为返回值,如上图所示
- 函数作为参数传递
//函数作为参数来传递
function F1() {
var a = 100;
return function(){
console.log(a)
}
}
var f1 = F1()
function F2(fn) {
var a = 200
fn()//100
}
F2(f1)
记住闭包是通过寻找其父级作用域的,而其父级作用域是定义它时的作用域,与执行作用域(如F2函数的作用域)没有关系,把握这点就不会出错。闭包通过以上两个例子,就基本概括了。
解题:
1、说一下对变量提升的理解
主要考查变量提升和函数声明(要注意函数表达式对比),即在一段<script>标签或一个函数内,在函数执行前,声明的变量会前置并首先赋值为undefined,如果是一个函数声明,则会把整个函数前置,如果是一个函数表达式,则同其他定义的变量一样,先赋值为undefined。函数内同理。
2、说明this的几种使用场景
- 作为构造函数执行
- 作为对象属性执行
- 作为普通函数执行
- call apply bind
3、如何理解作用域
关键3点:
- 自由变量
- 作用域链,即自由变量的查找
- 闭包的两个场景
首先什么是闭包?《JS高级程序设计》是这样定义的:闭包是指有权访问另一个函数作用域中的变量的函数。
在闭包中存在自由变量(即自身函数作用域为声明的变量),在函数执行时,自由变量会首先从自身作用域中查询是否存在该变量,如果没有找到该变量,则会进一步从父级作用域中去寻找,如果在父级作用域中没有找到,则从父级作用域的父级作用域中去寻找,这样就构成了作用域链,也即自由变量的查找。父级作用域是指函数定义时的作用域,即函数在哪定义,哪该函数的父级作用域就是哪,注意父级作用域不是执行时的作用域,是函数定义时的作用域。
闭包的两个使用场景:
- 函数作为返回值
- 函数作为参数传递
示例如上
4、闭包的实际应用场景
//闭包的实际应用,主要用于封装变量,收敛权限
function isFirstLoad() {
var _list = []
return function (id) {
if (_list.indexOf(id) != -1) {
return false
} else {
_list.push(id)
return true
}
}
}
var FirstLoad = new isFirstLoad()
FirstLoad(100)//true
FirstLoad(100)//false
FirstLoad(200)//true
//在isFirstLoad外无法修改_list的值,因此具有很高的安全性