项目开发
创建作用域链:当前变量、所有父级变量
创建变量:包括参数、变量声明(可变参数)、函数
创建上下文(context): this 当前环境下默认指向
执行过程中:
- 变量的赋值 -> 查找作用域链
- 变量的使用 -> 查找作用域链
- 函数的引用
JS 的单线程操作
回调栈 call stack
作用域 + 上下文
作用域链 - 找祖宗的过程
面试题:
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('test2', b); // 是否能找到?
}
}
}
console.log('test2', b);
course();
test1 肯定可以,创建的时候有b test2 不可以,因为在外层
-
- 对于作用域链,我们可以直接通过创建态来定位作用域链
-
- 手动取消全局,使用块级作用域
if (true) {
let e = 111;
console.log(e);
}
console.log('test3', e)
// 减少window 挂载
this 上下文 context
-
我家门前有条河,门前的河上有座桥,河里有群鸭
-
我家门前有条河,「这河」上有座桥,「这河」里有群鸭 -> this
-
this 是在执行时动态读取上下文决定的,而不是在创建时!!!
考察重点
函数的直接调用中 - this 指向的是 window => 变种:函数表达式、嵌套函数、匿名函数
function foo() {
console.log('函数内部this', this)
}
foo(); // 函数内部指向全局window
隐式绑定 -> 链接和绑定 - this 的指向是调用堆栈的上一级 => 变种:对象、数组等引用等关系逻辑
function fn() {
console.log('隐式绑定', this.a)
}
const obj = {
a: 1
}
obj.fn = fn;
obj.fn(); // 1
回到刚刚的例子
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('test2', b); // 是否能找到?
}
}
}
console.log('test2', b);
course();
作用域链查找:底层 -> 上层,且是创建态
obj: {
fn
}
obj 引用 fn 事实上引用的是 fn 的地址,地址中存的值是fn;那么在隐式绑定中,this 指向引用地址的人
面试题:
const foo = {
bar: 10,
fn: function () {
console.log(this.bar);
console.log(this);
}
}
// 1. 取出
let fn1 = foo.fn;
// 2. 执行
fn1(); // 执行并没有通过foo执行
// 执行环境会变!!!
追问1:如何改变指向?
const o1 = {
text: 'o1',
fn: function() {
// 直接使用上下文 - 传统分活
return this.text;
}
}
const o2 = {
text: 'o2',
fn: function() {
// 呼叫领导执行 - 部门协作
return o1.fn();
}
}
const o3 = {
text: 'o3',
fn: function() {
// 直接内部构造 - 公共人
let fn = o1.fn;
return fn();
}
}
console.log('o1fn', o1.fn());
console.log('o2fn', o2.fn()); // o1, 还是会指向o1
console.log('o3fn', o3.fn()); // undefined
-
- 在执行函数时,函数被上一级调用,上下文指向上一级
-
- or 直接变成公共函数,指向 window
追问:现在要将console.log('o2fn', o2.fn());的结果是o2,如何?
- 人为干涉,改变 this - bind/call/apply
- 不许改变 this
const o1 = {
text: 'o1',
fn: function() {
return this.text;
}
}
const o2 = {
text: 'o2',
fn: o1.fn
}
console.log('o2fn', o2.fn()); // o2, 这里this 指向最后调用它的对象,在执行 fn 时,o1.fn 抢过来直接挂载在自己的 o2fn 上即可
显式绑定 (bind | apply | call)
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
// 类方法中的 this
追问:类中异步方法,this 有区别吗?
class Course {
constructor(name) {
this.name = name;
console.log('构造函数中的 this', this)
}
test() {
console.log('类方法中的 this', this)
}
asyncTest() {
console.log('异步方法外的 this', this);
setTimeout(function() {
console.log('异步方法内的 this', this)
})
}
}
const course = new Course('this')
course.test();
course.asyncTest();
setTimeout 内部有单独执行的函数指向window
-
- 执行setTimeout时,匿名方法执行时,效果和全局执行函数效果相同
-
- 追问,如何解决?-> 利用箭头函数(不会影响this 指向)
bind 原理/ 手写bind
- 原理或者手写类题目,解题思路:
-
- 说明原理,写下注释
-
- 根据注释,补齐代码
// 辅助函数
function sum (a, b, c) {
console.log('sum', this);
return a + b + c;
}
// 1. 需求:手写bind => bind 位置(挂在哪里)=> 挂在 Function.prototype 上
// 2. bind 是什么?=> a. 返回一个未执行函数 b. 返回原函数执行结果 c. 传参保持不变
Function.prototype.newBind = function () {
// 拿到当前上下文
const _this = this;
const args = Array.prototype.slice.call(arguments);
// args 特点,第一项是新的 this , 第二项 ~ 最后一项为函数传参
const newThis = args.shift();
// a. 返回一个未执行函数
return function () {
// b. 返回原函数执行结果 c. 传参保持不变
return _this.apply(newThis, args);
}
}
实现 apply
Function.prototype.newApply = function (context) {
// 边缘检测
// 函数检测 防止显式call 调用 Function.prototype
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 = '信';
return function () {
console.log(content);
}
}
const envelop = mail();
envelop();
书从图书馆拿出来,让进不去图书馆的人看到书
- 函数外部获取到了函数作用域内的变量值
函数作为参数的场景
let content;
// 通用储存
function envelop(fn) {
content = 1;
fn();
}
// 业务逻辑
function mail() {
console.log(content);
}
envelop(mail);
模块作用单一职责原理,envelop 管理数据,mail 操作
函数嵌套
对外只暴露outerFn,内部有innerFn方法,但是内部逻辑看不见
let counter = 0;
function outerFn() {
function innerFn() {
counter++;
console.log(counter);
// ...
}
return innerFn;
}
outerFn()();
事件处理(异步执行)的闭包
let lis = document.getElementByTagNames('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 createIncrement(){
let count = 0;
function increment() {
count ++;
}
let message = `count is ${count}`;
function log() {
console.log(message);
}
return [increment, log]
}
const [increment, log] = createIncrement();
increment();
increment();
increment();
log(); // count is 0
// 外部控制内部的功能
实现私有变量
- 什么是私有变量? - 让当前变量隐藏起来
- js 中有没有私有变量?
- 如何实现?
function createStack() {
return {
items: [],
push(item) {
this.item.push(item);
}
}
}
const stack = {
items: [],
push: function () {
}
}
function createStack() {
const items = [];
// 内部维护,外部只读
return {
items: [],
push(item) {
items.push(item);
}
}
}
// Vuex store 通过暴露的方法dispatch 修改值,但是不能直接改