作用域,闭包,this
3.1 面试题
1.this 的不同应用场景,如何取值
2.手写 bind 函数
3.实际开发中闭包的应用场景,举例说明
4.程序题
// 打印输出
for (var i = 0; i < 10; i += 1) {
setTimeout(() => {
console.log(i);
}, 1000);
}
3.2 知识点
1.作用域和自由变量
2.闭包
3.this
3.3 作用域和自由变量
1.作用域
全局作用域
函数作用域
块级作用域(ES6 新增,即用大括号包含的范围)
2.自由变量
在当前作用域要用到的变量 x,并没有在当前中声明,要到上层作用域中找到他,这个变量 x 就是自由变量
const a = 'a';
{
// 在这个块级作用域未找到a,则向外层找直到找到全局作用域结束
console.log(a);
}
3.4 闭包
什么是闭包?概念太难去描述,现在排除掉闭包的定义,仅仅来看看闭包的实现以及其作用
1.实现闭包
1.将函数作为返回值
function create() {
const ele = 'element';
return function () {
console.log(ele);
}
}
const ele = 'new element'
const fn = create();
fn(); // element
2.将函数作为参数传入
function print(fn) {
const ele = 'element';
fn(ele);
}
const ele = 'new element';
function paramFn(param) {
console.log(param);
}
print(paramFn); // element
以上两种方式,
1.都可以读取函数 print 内部的变量 ele;
2.并且让这些变量的值始终保持在内存中,不会在 print 调用后被自动清除。
自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方!!!
function print(fn) {
const ele = 'element';
fn();
}
const ele = 'new element';
function paramFn() {
console.log(ele);
}
print(paramFn); // new element
3.5 this
this 取什么值,是在函数执行的时候确定的,不是在定义的时候确定!!!
1.当作普通函数被调用,this 指向 window
function fn() {
console.log(this);
}
fn(); // window
2.使用call, apply, bind,可以修改 this 指向
function fn() {
console.log(this)
};
fn.call({x: 'x', y: 'y', z: 'z'}); // {x: "x", y: "y", z: "z"}
fn.bind({x: 'x', y: 'y', z: 'z'})(); // {x: "x", y: "y", z: "z"}
fn.apply({x: 'x', y: 'y', z: 'z'}); // {x: "x", y: "y", z: "z"}
3.作为对象方法调用
3.1 在对象方法中使用setTimeout,这里面的this就和普通函数被调用一样(见第一点);而在 eat 函数本身里面 this 则指向对象本身
const zhangsan = {
name: '张三',
age: 16,
eat() {
console.log(this); // 对象本身
setTimeout(function() {
console.log(this); // window
}, 0);
},
}
zhangsan.eat();
3.2 在对象方法中使用setTimeout,但传入的是箭头函数,这是由于箭头函数内的 this 永远指向其外层第一个普通函数的 this
const zhangsan = {
name: '张三',
age: 16,
eat() {
console.log(this); // 对象本身
setTimeout(() => {
console.log(this); // 对象本身
}, 0);
},
}
zhangsan.eat();
4.在class的方法中使用
class People {
constructor(name) {
this.name = name;
}
say() {
console.log(this.name, this)
}
}
const zhangsan = new People('张三');
zhangsan.say(); // 张三 People {name: "张三"}
this 指向实例后的对象本身
5.箭头函数
箭头函数内的 this 永远指向其外层第一个普通函数的 this (参见第三点)
补充:在原型上使用this
class People {
constructor(name) {
this.name = name;
}
say() {
console.log(this.name, this)
}
}
const zhangsan = new People('张三');
zhangsan.say(); // 张三 People {name: "张三"}
zhangsan.__proto__.say(); // undefined {constructor: ƒ, say: ƒ}
可以看到,通过 __ proto __ 上,虽然通过原型链可以找到 say 这个函数,但是 由于 this 指向的实例后的实例对象本身,因此通过 __ proto __ 使用 say 函数,this上就没有了 name 这个属性,因此打印出来 undefined
3.6 题目解答
2.手写 bind 函数
Function.prototype.bindSelf = function () {
// 将参数解析为数组
const args = Array.prototype.slice.call(arguments);
// 提取首元素,按规定即为this所指向的对象
const newThis = args.shift();
// self,这个this即为当前对象
const self = this;
// bind 返沪一个函数,apply与call用法一致,区别在于参数的传递方式
return function () {
return self.apply(newThis, args);
}
}
function fn(a, b) {
console.log(this, a, b)
};
fn.bind({x: 'x', y: 'y', z: 'z'}, 'a', 'b')();
fn.bindSelf({x: 'x', y: 'y', z: 'z'}, 'a', 'b')();
3.闭包的应用
1.隐藏数据
2.如做一个简单的 cache 工具
function createCache() {
const data = {};
function set(key, val) {
data[key] = val;
};
function get(key) {
return data[key]
}
return {
set,
get,
}
}
const c = createCache();
c.set('a', 1);
c.set('b', 2);
console.log(c.get('a'), c.get('b'));
4.程序题
for (var i = 0; i < 10; i += 1) {
setTimeout(() => {
console.log(i);
}, 1000);
}
一秒钟之后会打印十个10。
这是因为,i 是一个全局变量,js执行速度非常快,1秒之内就会执行完 for 循环,这个时候 i 已经增加到10,再打印的时候就会打印出十个 10,
如何解决这个问题,给出两个方法:
1.使用闭包
for (var i = 0; i < 10; i += 1) {
(function(param) {
setTimeout(() => {
console.log(param);
}, 1000);
})(i);
}
2.使用 let
for (let i = 0; i < 10; i += 1) {
setTimeout(() => {
console.log(i);
}, 1000);
}
欢迎大家点赞,收藏,关注!!!