一、作用域 + 上下文
作用域链
-
- 对于作用域链我们直接通过创建态来定位作用域链
-
- 手动取消全局,使用块级作用域
let a = 'global';
console.log(a);
function course() {
let b = 'zhaowa';
console.log(b);
session();
function session() {
let c = 'this';
console.log(c);
teacher();
function teacher() {
let d = 'yy';
console.log(d);
console.log('test1', b);
}
}
}
console.log('test2', b);
course();
if(true) {
let e = 111;
console.log(e);
}
console.log('test3', e)
二、this 上下文context
- this是在执行时动态读取上下文决定的,而不是创建时
1.函数直接调用
- this 指向的是window => 函数表达式、匿名函数、嵌套函数
function foo() {
console.log(this);
}
foo(); // Window
2.隐式绑定
- this的指向是调用堆栈的上一级 => 对象、数组等引用关系逻辑
function fn() {
console.log('隐式绑定', this.a);
}
const obj = {
a: 1,
fn
}
obj.fn = fn;
obj.fn();
面试题:
// 第一题
const foo = {
bar: 10,
fn: function() {
console.log(this.bar);
console.log(this);
}
}
let fn1 = foo.fn;
fn1(); // undefined Window
// 第二题, 如何改变指向
const o1 = {
text: 'o1',
fn: function() {
// 直接使用上下文 - 传统分活
return this.text;
}
}
// 1. 在执行函数时,函数被上一级调用,上下文指向上一级
const o2 = {
text: 'o2',
fn: function() {
// 呼叫领导执行 - 部门协作
return o1.fn();
}
}
// 2. 直接变成公共函数,指向window
const o3 = {
text: 'o3',
fn: function() {
// 直接内部构造 - 公共人
let fn = o1.fn;
return fn();
}
}
console.log('o1fn', o1.fn()); // o1
console.log('o2fn', o2.fn()); // o1
console.log('o3fn', o3.fn()); // undefined
// 第三题-现在我要将console.log('o2fn', o2.fn())的结果是o2
// 1. 人为干涉,改变this - bind/call/apply o1.fn.apply(o2);
// 2. 不改变this
// this指向最后调用他的对象,在fn执行时,o1.fn抢过来挂载在自己o2fn上即可
const o1 = {
text: 'o1',
fn: function() {
// 直接使用上下文 - 传统分活
return this.text;
}
}
// 1. 在执行函数时,函数被上一级调用,上下文指向上一级
const o2 = {
text: 'o2',
fn: o1.fn
}
console.log('o2fn', o2.fn()); // o2
显示绑定
function foo() {
console.log('函数内部this', this);
}
foo();
// 使用
foo.call({a: 1});
foo.apply({a: 1});
const bindFoo = foo.bind({a: 1});
bindFoo();
面试题-call、apply、bind的区别
-
- call < = > apply 传参不同 依次传入/数组传入
-
- bind返回函数改变指向后的函数,不执行
new-this指向的new创建的实例
class Course {
constructor(name) {
this.name = name;
console.log('构造函数中的this:', this);
}
test() {
console.log('类方法中的this:', this);
}
}
const course = new Course('this');
course.test();
面试题- 类中的异步方法,this有区别吗
-
- 执行setTimeout时,匿名方法执行时,效果和全局执行函数效果相同
-
- 再追问,如何解决?--箭头函数
class Course {
constructor(name) {
this.name = name;
console.log('构造函数中的this:', this);
}
test() {
console.log('类方法中的this:', this);
}
asyncTest() {
console.log('异步方法外:', this);
setTimeout(function() {
console.log('异步方法内:', this);
}, 100)
}
}
const course = new Course('this');
course.test();
course.asyncTest();
三、手写一个bind/apply
- 原理或者手写类题目,结题思路
-
- 说明原理,写下注释
-
- 根据注释,补齐代码
手写bind-
function.bind(thisArg[, arg1[, arg2[, ...]]])
- 根据注释,补齐代码
手写bind-
// 函数原型上的方法
Function.prototype.newBind = function(){
// 这里的this就是执行函数
const fn = this;
// 第一个参数是新的上下文,后面的参数是执行函数的参数
const arg = Array.prototype.slice.call(arguments); // 由于argument是类数组
const context = arg.shift();
// 返回新函数
return function() {
return fn.apply(context,arg);
}
}
手写apply-`function.apply(thisArg, args)
// 函数原型上的方法
Function.prototype.newApply = function(){
// 这里的this就是执行函数
const fn = this;
// 第一个参数是新的上下文,后面的参数是执行函数的参数
const context = arguments[0] || window;
context.fn = fn;
// 返回新函数执行结果
let result = arguments[1] ? context.fn(...arguments[1]) : context.fn();
delete context.fn;
return result;
}
四、如何突破作用域束缚-闭包
- 闭包:一个函数和他周围状态的引用捆绑在一起的组合
函数作为返回值的场景
- 函数外部获取到了函数作用域内的变量值
function mail() {
let content = '信';
return function() {
console.log(content);
}
}
const envelop = mail();
envelop();
函数作为参数的时候
// 单一职责
let content;
// 通用存储
function envelop(fn) {
content = 1;
fn();
}
// 业务逻辑
function mail() {
console.log(content);
}
envelop(mail);
函数嵌套
let counter = 0;
function outerFn() {
function innerFn() {
counter++;
console.log(counter);
// ...
}
return innerFn;
}
outerFn()();
事件处理(异步执行)的闭包
let lis = document.getElementsByTagName('li');
for(var i = 0; i < lis.length; i++) {
(function(i) {
lis[i].onclick = function() {
console.log(i);
}
})(i);
}
立即执行嵌套
(function immediateA(a) {
return (function immediateB(b) {
console.log(a); // 0
})(1);
})(0);
当立即执行遇上块级作用域
let count = 0;
(function immediate() {
if(count === 0) {
let count = 1;
console.log(count);
}
console.log(count);
})();
实现私有变量
function createStack() {
return {
items: [],
push(item) {
this.item.push(item);
}
}
}
const stack = {
items: [],
push: function() {}
}
function createStack() {
const items = [];
return {
push(item) {
items.push(item);
}
}
}
// Vuex store