• 上下文和作用域


    新的声明方式,新的声明方式带来了什么?块级作用域
    在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也可以按上面这么改

  • 相关阅读:
    javascript编程——闭包概念
    Chromium源码编译和初步的代码阅读
    No Code 趋势小记
    Electron中require报错的解决与分析
    C# 值类型与引用类型
    C# 静态成员 和 实例成员
    C# 标识符 和 关键字
    C# 基础知识
    Taghepler
    JQuery 速查表
  • 原文地址:https://www.cnblogs.com/pengdt/p/12037964.html
Copyright © 2020-2023  润新知