执行上下文
定义:当前JS代码执行时所在的环境,由JS引擎创建并管理
-
ES3:this、VO变量对象、作用域链
-
ES6:this、词法环境、变量环境
变量沿作用域链查找,this根据执行位置
词法/变量环境
相同:环境记录+外部引用(即VO+作用域链)
不同:词法let/const+函数声明,变量var
变量提升
js解析变量:声明、初始化、赋值阶段
创建上下文时:let/const(uninitialized),var(undefined)
∴ var可以在赋值前访问,let/const暂时性死区
作用域链
作用域定义:变量声明和作用的范围
静态/词法作用域:函数作用域在定义时决定
闭包
定义:能访问上级函数作用域中变量(即使已经销毁)的函数。
原因:子函数执行上下文维护了一条作用域链(只与定义的作用域有关):上级作用域内变量,被下级作用域内引用,未被释放,需要等待下级执行完毕才能释放
形成闭包:
实际应用:柯里化、节流防抖、
this
定义:在函数内部,指代当前环境
函数由全局对象/对象调用,只需看()的前缀
const apple = {
price: 10,
getPrice: function(){
function discount(){ console.log(this); }
discount(); //无前缀,全局对象调用
}
};
apple.getPrice(); //结果:window
原因:取决于函数的调用关系:函数单独存储,可以在不同上下文/环境执行
动态作用域:函数作用域在调用时决定
绑定方式
-
默认绑定:window全局对象(严格模式undefined)
-
隐式绑定:fn1 = obj.fn 会丢失this=obj
-
显式绑定:call、apply
-
new绑定:指向实例
-
箭头函数:指向外层词法作用域
手写
obj.fn() 给定环境,执行函数 → 给定函数this,指定环境
fn.call(obj, arg1,arg2)
fn.apply(obj, [arg1,arg2]) 参数为数组
fn.bind(obj, arg1,arg2)() 返回一个函数,等待执行
call
Function.prototype.myCall = function (ctx, ...args) {
ctx = ctx ? Object(ctx) : globalThis; //包装原始类型、判断null
const fn = Symbol(); //唯一属性名
ctx[fn] = this; //在环境中新增属性方法
let res = ctx[fn](...args); //传参,执行
delete ctx[fn]; //删除属性
return res; //返回
}
apply
Function.prototype.myApply = function (ctx, arr) {
ctx = ctx ? Object(ctx) : globalThis;
const fn = Symbol();
ctx[fn] = this;
let res;
if(!arr) { res = ctx.fn(); }
else { res = ctx.fn(...arr); } //展开数组
delete ctx.fn;
return res;
}
new
function myNew() {
Constructor = [].shift.call(arguments); //获取构造函数
var obj = Object.create(Constructor.prototype) //创建临时对象,连接原型,访问构造函数原型上的属性
var res = Constructor.apply(obj, arguments); //绑定this,访问构造函数上的属性
return res instanceof Object ? res : obj;
}
bind
var bindCtx = fn.bind(ctx, 'daisy'); → 绑定this=ctx
-
直接调用:bindCtx('18');
-
new调用:var obj = new bindCtx('18'); → 绑定this=obj,而非ctx
Function.prototype.myBind = function (ctx) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this; //缓存this=调用者
var args = Array.prototype.slice.call(arguments, 1); //bind接收参数
var fNOP = function () {}; //中转构造函数,实现从原函数原型继承
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments); //返回的函数接收参数
return self.apply(
this instanceof fNOP ? this : ctx, //造函数or普通函数,this指向不同
args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP(); //fBound.prototype=Object.create(this.prototype)
return fBound;
}
原型关系:fBound → new fNOP(),fNOP与bar同级并原型相同
避免修改fBound.prototype会同时修改bar.prototype,而是修改了中转空实例
原型
对象从原型中继承属性方法
每个对象都有__proto__,只有函数都有prototype
继承
执行步骤
进入代码,创建全局上下文,入执行栈,上下文初始化;
调用函数,创建函数上下文,入执行栈,上下文初始化,执行……
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope; //return this.scope;
}
return f;
}
return scope; 根据函数定义时的位置(闭包)
checkscope()(); // local scope
var foo = checkscope(); // local scope 区别:foo指向f,不会释放f
foo();
return this.scope; 根据函数调用时的位置
checkscope()(); // global scope
参考