课程目标
- this
- 闭包
- 作用域
知识点
- 作用域 + 上下文
- 闭包常见场景
作用域链
先看一段简单的代码:
let a = 'global';
console.info(a);
function course() {
let d = 'd';
console.info(d);
session();
function session() {
let b = 'this';
console.info(b);
teacher();
function teacher() {
let c = 'yy';
console.info(c);
console.info(d);
}
}
}
course();
是能正常输出的:如下图
- 对于作用域链我们直接通过创建态来定位作用域链
- 手动取消全局,使用块级作用域
this 上下文context
this是在执行时动态读取上下文决定的,而不是创建时。
函数直接调用中
- this指向的是window => 函数表达式、匿名函数、嵌套函数
function foo() {
console.info('函数内部this', this);
}
foo();
隐式绑定
- this的指向是调用堆栈的上一级 => 对象、数组等引用关系逻辑
function fn() {
console.info('隐式绑定', this.a);
}
const obj = {
a: 1
}
obj.fn = fn;
obj.fn();
输出结果:
- 其实执行到
obj.fn = fn这句代码就相当于是:
const obj = {
a: 1,
fn
}
- 面试题:this的输出结果是什么?
const foo = {
bar: 10,
fn: function() {
console.info(this.bar);
console.info(this);
}
}
let fn1 = foo.fn;
fn1();
输出结果:
从结果能看到
this指向window是因为fn1只是把foo.fn方法取出并没有执行。fn1()后面单独执行。
- 追问:如何改变指向
const a1 = {
text: 'a1',
fn: function() {
//传统。直接使用上下文
return this.text
}
}
const a2 = {
text: 'a2',
fn: function() {
//间接使用上下文
return a1.fn()
}
}
const a3 = {
text: 'a3',
fn: function() {
//直接内部构造 - 公共
let fun = a1.fn;
return fun()
}
}
console.info('a1fn:',a1.fn());
console.info('a2fn:',a2.fn());
console.info('a3fn:',a3.fn());
输出结果:
- 在执行函数时,函数被上一级调用,上下文指向上一级
- or 直接变成公共函数,指向window
改变this
this指向最后调用他的对象.
const a1 = {
text: 'a1',
fn: function() {
//传统。直接使用上下文
return this.text
}
}
const a2 = {
text: 'a2',
fn: a1.fn
}
console.info('a2fn:',a2.fn());
显示绑定 - bind/call/apply
使用方法:
function foo() {
console.info('函数内部this',this);
}
foo();
foo.call({a: 1}):
foo.apply({a: 1});
const bindFoo = foo.bind({a: 1});
bindFoo();
call、apply、bind的区别
- call、apply传参不同,
- call 依次传入
- apply 数组传入
- bind 直接返回不同
new - this指向的是new之后得到的实例
class Course {
constructor(name) {
this.name = name;
console.info('构造函数中的this',this);
}
test() {
console.info('类方法中的this',this);
}
}
const course = new Course('this');
course.test();
类中异步方法,this有区别吗?
class Course {
constructor(name) {
this.name = name;
console.info('构造函数中的this',this);
}
test() {
console.info('类方法中的this',this);
}
asyncTest() {
console.info('异步方法外的this',this);
setTimeout(function() {
console.info('异步方法内的this',this);
},1000);
}
}
const course = new Course('this');
course.test();
course.asyncTest();
- 执行setimeout时,匿名方法执行时,效果和全局执行函数效果相同。
- 再追问:如果把setimeout的this改成指向实例怎么办?改成箭头函数
class Course {
asyncTest() {
console.info('异步方法外的this',this);
setTimeout(()=> {
console.info('异步方法内的this',this);
},1000);
}
}
const course = new Course('this');
course.asyncTest();
bind的原理/手写bind
- 说明原理,写下注释
- 根据注释,补齐代码
function sum(a,b,c) {
return a + b + c;
}
//1. bind挂载在哪里?- Function.prototype
Function.prototype.newBind = function() {
//2. bind是什么?- (1)返回一个函数,(2)返回原函数执行结果,(3)传参不变
const _this = this;
const args = Array.prototype.slice.call(arguments);
//arguments的特点:第一项是新的this,第二项是最后一项函数传参
const newThis = args.shift();
return function() {
return _this.apply(newThis, args);
}
}
实现apply
Function.prototype.newApply = function(context) {
//边缘测试
//函数测试
if (typeof this !== 'function') {
throw new TypeError('Error');
}
//参数测试
context = context || window;
//挂载执行函数
context.fn = this;
//执行函数
let result = arguments[1]
? context.fn(...arguments[1])
: context.fn();
//销毁临时挂载
delete context.fn;
return result;
}
如何突破作用域束缚 - 闭包
闭包
闭包:一个函数和他周围状态的引用捆绑在一起的组合
函数作为返回值的场景
function mail() {
let content = 'mail';
return function() {
console.info(content);
}
}
const envelop = mail();
envelop();
通过上面的例子可以看出:函数外部获取到了函数作用域内的变量值
函数作为参数的时候
//单一职责
let content;
//通用存储
function envelop(fn) {
content = 1;
fn();
}
//业务逻辑
function mail() {
console.info(content);
}
envelop(mail);
函数嵌套
let counter = 0;
function outerfn() {
function innerfn() {
counter++;
console.info(counter);
}
return innerfn;
}
outerfn()();
事件处理(异步执行)的闭包
let lis = document.getElementsByTagName('li');
for(let i = 0; i < lis.length; i++){
(function(i) {
lis[i].onclick = function(){
console.info(i);
}
})(i);
}
立即执行嵌套
(function immediateA(a) {
return (function immediateB(b) {
console.info(a);//0
})(1);
})(0);
当立即执行遇上块级作用域:
let count = 0;
(function immediate() {
if(count === 0) {
let count = 1;
console.info(count);
}
console.info(count);
})();
拆分执行
function createI() {
let count = 0;
function increment() {
count++;
}
let message = `count is ${count}`;
function info() {
console.info(message);
}
return [increment, info]
}
const [increment, info] = createI();
increment();
increment();
increment();
info();
- 为什么没有新增成功?
- 因为改变的是内部变量count ,打印的是内部变量message,实际传递出来的是函数。
实现私有变量
function create() {
return {
items: [],
push(item) {
this.items.push(item);
}
}
}
const stack = {
items: [],
push: function() {}
}
function createS() {
//私有变量
const items = [];
return {
push(item) {
items.push(item);
}
}
}