JavaScript 执行上下文、作用域、this、原型

322 阅读3分钟

执行上下文

定义:当前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

image.png

继承


执行步骤

进入代码,创建全局上下文,入执行栈,上下文初始化;

调用函数,创建函数上下文,入执行栈,上下文初始化,执行……

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

参考

JavaScript执行上下文之作用域链,闭包和this(四)_feral_coder的博客-CSDN博客