上下文和作用域

新的声明方式,新的声明方式带来了什么?块级作用域
在let之前,js只有全局作用域和函数作用域,先上个栗子吧

if(false){
   var aa = 111;
}
console.log(aa) // 111

for(var i=0;i<5;i++){
   var bb = 222;
}
console.log(i) //5
console.log(bb)  //222

其实从if这个单词开始到大括号结束,从for这个单词开始到大括号结束是有作用域的,叫做块级作用域块级作用域用逻辑判断跳出,return不能跳出块级作用域,只能跳出函数作用域,他能被外部访问是因为var的变量提升和函数提升,除掉一个习惯,函数的声明别放在if和for里,那就只剩变量提升了,如果把上面的代码改为let,const跟let一样的,不讲是因为没人会把const当变量

if(false){
   let aa = 111;
}
console.log(aa) // 错误

for(let i=0;i<5;i++){
   let bb = 222;
}
console.log(i) //错误
console.log(bb)  //错误

let让上面的aa,i,bb只能在那个小小的作用域内使用,如果同个函数作用域或者全局作用域有一样的变量,会安全很多,如果觉得没什么意义的话,就接着用var,没区别

let/const跟var有什么区别

// 面试题来着
// 这里是全局作用域window
var aa = "aa"
let bb = "bb"
console.log(window.aa)  //"aa"
console.log(window.bb)  //undefined
console.log(window.cc)  //undefined
console.log(aa)  //"aa"
console.log(bb) //"bb"
// bb并不在window里,因为他在自己的作用域里,只能用变量名去取值

所以目前已知作用域为全局作用域,函数作用域,块级作用域,用代码模拟就是

// 当页面被打开,就有一个全局作用域window
window = {
  // 还有页面里所有的dom和bom
  var dom = {}
  var bom = {}

  // 下面的内容就是程序员自己的代码或者插件了
  var name = "tom"
  function aa(){
    // 函数作用域
    if(true){
      // 块级作用域
      let name = "jee"
    }
    console.log(name) //"tom"
  }
}

上下文
回顾一个函数作用域里隐藏了什么东西
有个隐藏的arguments参数伪数组,如果是dom时间,还有个event事件对象,并且还有个肯定有的this上下文,函数被执行肯定有个执行者,执行者就是这个函数的this

// 这个都理解不了就转行吧
var name = "我是window"
function init(){
   console.log(this)  // window
   console.log(this.name)  // "我是window"
}
init()  //实际是window.init()
// 这个都理解不了就转行吧
var obj = {
   name: "我是obj",
   init(){
      console.log(this)  // obj
      console.log(this.name)  // "我是obj"
   }
}
obj.init()

如果我不想把init写在obj里,因为window我也要用,重复写很不好怎么办
用function的方法apply,call,bind
这三兄弟是面试的经典

var name = "我是window"
var obj = {
   name: "我是obj",
}
// 加两个
function init(x,y){
   console.log(x,y)
   console.log(this)
   console.log(this.name)
}
// 直接执行init肯定就是window了
init(1,2)
// 修改this方式一,立即执行
init.call(obj,1,2)
// 修改this方式二,立即执行
init.apply(obj,[1,2])
// 修改this方式三,不立即执行,后续执行
init.bind(obj)(1,2)

说apply
因为apply可以把参数变成数组,所以这个强大的优点让人们忘了他实际是用来改变this的

Math.max.apply(null, [14, 3, 77]) //第一个参数是空因为Math本来就是window的子对象
arr1.push.apply(arr1,arr2) //第一个参数需要是this自己,用null就没了
// 还可以这么写
Array.prototype.push.apply(arr1, arr2);
... 
//一切可以无限传参数的方法都可以用apply改成传数组

但是apply被拓展运算符代替了

Math.max(...[14, 3, 77])
arr1.push(...arr2);
arr1.push(...arguments);

保留this
在函数里执行函数肯定是window

function init(){
   console.log(this)
}
document.querySelector("#id").onclick = function(){
   console.log(this) //dom#id....
   console.log(this.style.color)
   init() // 我们总是以为这个是当前this执行的,但实际是window执行的
}

为了让init能拿到前一个函数的this,最常见的做法是var that = this
然后把that当做参数传过去,这个是没问题的,只是维护很难,很吐血,判断很多

function cb(that){
   console.log(that)
}
var obj = {
   name: "我是obj",
   init(cb){
      console.log(this)
      var that = this; 
      cb(that)
   }
}
obj.init(cb)

上面的call,apply,bind直接修改上下文this,而不是把上下文当做参数去传递

function cb(that){
   console.log(that)
}
var obj = {
   name: "我是obj",
   init(cb){
      console.log(this)
      cb.call(this)
   }
}
obj.init(cb)

箭头函数也可以保存this,但是必须声明在父函数内部

var obj = {
   name: "我是obj",
   init(){
      console.log(this)
      // 箭头函数执行是往上找执行者,直到找到一个执行者后停下来
      // 如果如果上一级也是箭头函数就继续往上找
      // 箭头函数没有三个改变this的方法
      // 一般也不会在这里声明一个cb函数,cb函数一般都是作为参数传进来的
      var cb = () => { console.log(this) };
      cb()  // obj 
   }
}
obj.init()

箭头函数在定时器效果最明显

var name = "name1";
function init(){
   var name = "name2";
   // 这个改的是window的name
   setTimeout(function(){
      this.name = "new"
   },2000)
   // 这个改的是上面的name
   setTimeout(()=>{
      this.name = "new"
   },2000)
}

this的使用是很少的,也不会需要经常去修改this,自定义插件就用的挺多的

上班的时候学习java后自己做了个js的面向AOP开发的功能

function aop(funArr,beforeFun,afterFun,context){
    var thatContext = context;
    var that = this;
    this.init = function(){
        that.funArrEach(funArr,thatContext);
        return that;
    }
    this.funArrEach = function(arr,context){
        arr.forEach(function(fun){
            that.addAop(fun,context)
        })
    }
    this.addAop = function(fun,context){
        var context = context || window;
        context[fun.name] = function(){
           beforeFun()
           fun.call(context,...arguments)
           afterFun()
        }
    }
    this.push = function(opt,context){
        var context = context || thatContext;
        if(Object.prototype.toString.call(opt)=="[object Array]"){
            that.funArrEach(opt,context);
        }
        if(Object.prototype.toString.call(opt)=="[object Function]"){
            that.addAop(opt,context);
        }
    }
}

function aopBeforeFun() { ... }

function aopAfterFun() { ... }

new aop([a,b,c],aopBeforeFun,aopAfterFun).init()

改进版,有时候aopBefore需要判断是否执行

this.addAop = function(fun,context){
    var context = context || window;
    context[fun.name] = function(){
       if(beforeFun(fun,context,arguments)){

       }else{
          fun.call(context,...arguments)
       }
       afterFun()
    }
}
function aopBeforeFun(cb,context,arg) {
    if(true){
      // 自己执行
      cb.call(context,...arg)
      return true;
    }else{
      // 跳过不执行了
      return true
    }    
}

aopAfter也可以按上面这么改

原文地址:https://www.cnblogs.com/pengdt/p/12037964.html