【前端面试】作用域和闭包

28次阅读

共计 3896 个字符,预计需要花费 10 分钟才能阅读完成。

1. 题目
说一下对变量提升的理解
说明 this 的几种不同使用场景
创建 10 个 a 标签,点击的时候弹出来相应的序号
如何理解作用域
实际开发中闭包的应用
2. 知识点
2.1 执行上下文
范围:一段 script 或者一个函数
全局:变量定义、函数声明 script
函数:变量定义、函数声明、this、arguments(执行之前)
函数声明和函数表达式的区别:
a(); // 报错 函数表达式 变量声明 会提前。
var a = function(){}

b(); // 不报错 函数声明
function b(){}
变量定义时会默认把他的变量声明提升:(仅限于他的执行上下文,比如一段 script 和一个函数中)
console.log(a);
var a = 0;
实际上是
var a;
console.log(a);
a = 0;
2.2 this
this 要在执行时才能确认,定义时无法确认。
var a = {
name:’a’,
fn:function(){
console.log(this.name);
}
}

a.fn(); // a
a.fn.apply({name:’b’}); // b a.fn.call({name:’b’});
var fn1 = a.fn();
fn1(); // undefined
this 的使用场景
构造函数中(指向构造的对象)
function Fun(name){
this.name = name;
}
var f = new Fun(‘a’);
console.log(f.name);
对象属性中(指向该对象)
普通函数中(指向 window)
call apply bind
var fun = function (name){
console.log(this);
console.log(name);
}.bind({a:1});
fun(“name”);
arguments 中的 this:
var length = 10;
function fn(){
alert(this.length)
}
var obj = {
length: 5,
method: function(fn) {
arguments[0]()
}
}
obj.method(fn)// 输出 1 这里没有输出 5,也没有输出 10,反而输出了 1,有趣。这里 arguments 是 javascript 的一个内置对象(可以参见 mdn:arguments – JavaScript),是一个类数组(就是长的比较像数组,但是欠缺一些数组的方法,可以用 slice.call 转换,具体参见上面的链接),其存储的是函数的参数。也就是说,这里 arguments[0] 指代的就是你 method 函数的第一个参数:fn,所以 arguments[0]() 的意思就是:fn()。
不过这里有个疑问,为何这里没有输出 5 呢?我 method 里面用 this,不应该指向 obj 么,至少也会输出 10 呀,这个 1 是闹哪样?
实际上,这个 1 就是 arguments.length,也就是本函数参数的个数。为啥这里的 this 指向了 arguments 呢?因为在 Javascript 里,数组只不过使用数字做属性名的方法,也就是说:arguments[0]() 的意思,和 arguments.0() 的意思差不多(当然这么写是不允许的),你更可以这么理解:
arguments = {
0: fn, // 也就是 functon() {alert(this.length)}
1: 第二个参数, // 没有
2: 第三个参数, // 没有
…,
length: 1 // 只有一个参数
}
所以这里 alert 出来的结果是 1。
如果要输出 5 应该咋写呢?直接 method: fn 就行了。
2.3 作用域
没有块级作用域
if(true){
var name = “test”
}
console.log(name);
尽量不要在块中声明变量。
只有函数级作用域
2.4 作用域链
自由变量 当前作用域没有定义的变量 即为自由变量。
自由变量会去其父级作用域找。是定义时的父级作用域,而不是执行。
var a = 100;
function f1(){
var b = 200;
function f2(){
var c = 300;
console.log(a); // 自由变量
console.log(b); // 自由变量
console.log(c);
}
f2();
};
f1();
2.5 闭包
一个函数中嵌套另外一个函数,并且将这个函数 return 出去,然后将这个 return 出来的函数保存到了一个变量中,那么就创建了一个闭包。
闭包的两个使用场景
1. 函数作为返回值
function fun(){
var a = 0;
return function(){
console.log(a); // 自由变量,去定义时的父级作用域找
}
}

var f1 = fun();
a = 1000;
f1();

2. 函数作为参数
function fun(){
var a = 0;
return function(){
console.log(a); // 自由变量,去定义时的父级作用域找
}
}

function fun2(f2){
a = 10000
f2();
}

var f1 = fun();

fun2(f1);

具体解释看 高级 - 闭包中的说明
闭包的两个作用:
能够读取其他函数内部变量的函数
可以让函数内部的变量一直保存在内存中
实际应用场景 1:
闭包可以将一些不希望暴露在全局的变量封装成“私有变量”。
假如有一个计算乘积的函数,mult 函数接收一些 number 类型的参数,并返回乘积结果。为了提高函数性能,我们增加缓存机制,将之前计算过的结果缓存起来,下次遇到同样的参数,就可以直接返回结果,而不需要参与运算。这里,存放缓存结果的变量不需要暴露给外界,并且需要在函数运行结束后,仍然保存,所以可以采用闭包。
上代码:
function calculate(param){
var cache = {};
return function(){
if(!cache.parame){
return cache.param;
}else{
// 缓存计算 ….
//cache.param = result
// 下次访问直接取
}
}
}

实际应用场景 2
延续局部变量的寿命
img 对象经常用于进行数据上报,如下所示:
var report = function(src){
var img = new Image();
img.src = src;
};
report(‘http://xxx.com/getUserInfo’);
但是通过查询后台的记录我们得知,因为一些低版本浏览器的实现存在 bug,在这些浏览器下使用 report 函数进行数据上报会丢失 30% 左右的数据,也就是说,report 函数并不是每一次都成功发起了 HTTP 请求。
丢失数据的原因是 img 是 report 函数中的局部变量,当 report 函数的调用结束后,img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP 请求,所以此次请求就会丢失掉。
现在我们把 img 变量用闭包封闭起来,便能解决请求丢失的问题:
var report = (function(){
var imgs = [];
return function(src){
var img = new Image();
imgs.push(img);
img.src = src;
}
})();
闭包缺点:浪费资源!
3. 题目解答
3.1 说一下对变量提升的理解
变量定义和函数声明
注意函数声明和函数表达式的区别
变量定义时会默认把他的变量声明提升:(仅限于他的执行上下文,比如一段 script 和一个函数中)
console.log(a);
var a = 0;
实际上是
var a;
console.log(a);
a = 0;
3.2 说明 this 的几种不同使用场景

构造函数中(指向构造的对象)
对象属性中(指向该对象)
普通函数中(指向 window)
call apply bind

3.3 创建 10 个 a 标签,点击的时候弹出来相应的序号
实现方法 1:用 let 声明 i
var body = document.body;
console.log(body);
for (let i = 0; i < 10; i++) {
let obj = document.createElement(‘i’);
obj.innerHTML = i + ‘<br>’;
body.appendChild(obj);
obj.addEventListener(‘click’,function(){
alert(i);
})
}
实现方法 2 包装作用域
var body = document.body;
console.log(body);
for (var i = 0; i < 10; i++) {
(function (i) {
var obj = document.createElement(‘i’);
obj.innerHTML = i + ‘<br>’;
body.appendChild(obj);
obj.addEventListener(‘click’, function () {
alert(i);
})
})(i)
}

3.4 实际开发中闭包的应用
能够读取其他函数内部变量的函数
可以让函数内部的变量一直保存在内存中
封装变量,权限收敛
应用 1
var report = (function(){
var imgs = [];
return function(src){
var img = new Image();
imgs.push(img);
img.src = src;
}
})();
用于防止变量销毁。
应用 2
function isFirstLoad() {
var arr = [];
return function (str) {
if (arr.indexOf(str) >= 0) {
console.log(false);
} else {
arr.push(str);
console.log(true);
}
}
}

var fun = isFirstLoad();
fun(10);
fun(10);
将 arr 封装在函数内部,禁止随意修改,防止变量销毁。

正文完
 0