JavaScript 之 this 指向

在这里插入图片描述

一、this 引入

1.1 引入

  • 普通函数中都有一个this变量:一般情况指向某一个对象
    • 箭头函数没有 this
    • 谁调用就指向谁

1.2 现象理解

<script>
    // 普通函数中都有一个this变量:在大多数情况下会指向一个对象
    // arguments:保存的是传入的所有参数
    // 1. 情况一:普通函数被默认调用,this指向window
    function foo(name, age) {
      console.log(arguments);
      console.log(this);//window 对象
    }
    foo("lili", 123)
    function sayHello() {
      console.log(this);
    }
    sayHello()// window


    // 2. 情况二:函数是被某一个对象引用并且调用它,this会指向这个对象(调用的那个对象)
    var obj = {
      // 函数没有谁属于谁,看谁调用的,谁就是this
      running: function () {
        console.log("I LIKE RUNNING");
        console.log(this === obj);
      }
    }
    obj.running()//true
    // 将running这个函数赋值给fn
    // 引用的赋值
    // 题目一:
    var fn = obj.running
    fn()//window
    // 题目二:
    function bar() {
      console.log("bar function~~");
      console.log(this);
    }
    var person = {
      name: "lili",
      bar: bar
    }
    person.bar()//person
  </script>

1.3 为什么要使用 this

  • 开发中作用:方便在一个方法拿到当前对象的一些属性
    • 当前对象不一定是 info
    • 不受外界变量的影响
  • 不然就是每次传入参数
<script>
    // 对象没有作用域,只是一种数据类型
    //{key:value}:对象不是代码块
    var info = {
      name: "lili",
      running: function () {
        console.log(this.name + " is running");
      },
      eating: function () {
        console.log(this + " is eating");
      },
      studying: function () {
        console.log(this + " is studying");
      }
    }
    info.running()
    info.eating()
    info.stydying()
  </script>

二、开启 this 之旅

2.1 this 的绑定规则

  • 调用:执行

  • 函数在调用时, JavaScript 会默认给 this 绑定一个值

  • this 的绑定和定义的位置(编写的位置)没有关系

  • this 的绑定和调用方式以及调用的位置有关

    • 调用方式
    • 调用位置
  • this 是在运行时被绑定的

    • 跟参数类似

    this的绑定规则

  • 默认绑定独立函数调用: window ,就是普通的一个函数进行调用

    • 普通的函数被独立的调用
    • 函数定义在对象中,进行赋值然后独立调用
    • 高阶函数
    • 严格模式下, 独立调用的函数中的 this 指向的是 undefined
     // "use strict"
    function foo(){
    console.log("1. 独立函数调用,对象是window");
    }
    foo()//对象是 window
    
    // 2. 函数定义在对象中,但是独立调用
    var obj={
        name:"lili",
        bar:function(){
        console.log("谁调用:",this);
        }
    }
    obj.bar()//对象调用
    
    var baz= obj.bar//函数赋值
    baz()//独立函数调用 window
    
      //  3.高阶函数
      function test(fn){
        fn()
      }
      test(obj.bar)//把这个值赋值给fn了 window
    //  4. 严格模式下,独立调用的函数中的this指向的是underfined
    
    • 隐式绑定:通过某个对象进行调用,js 引擎默认绑定到函数调用者
       function foo(){
        console.log("foo函数:",this);
      }
      var obj={
        bar:foo
      }
      obj.bar() // obj
    
    • 显式绑定:明确告诉它指向谁
    function foo(){
       console.log("foo函数:",this);//强制this指向obj
    }
    var obj={
      name:"lili"
    }
    //可能会这么做
    // obj.foo=foo//添加属性到obj
    // obj.foo() 
    // 思考:直接执行函数并且强制 this 就是 obj 对象
    // foo.apply,与foo.call区别很小
    foo.call(obj)
    // 传入为普通类型-->创建对应的包装类型-->作为 this
    //foo.call(undefined)
    // 没有包装类型-->指向window
    
    • new 绑定
       function foo(){
        console.log("foo函数:",this);
      }
      // new:
      // 1. 创建新的空对象
      // 2. 将this指向这个空对象
      // 3. 执行函数中的代码
      // 4. 没有显式返回非空对象时候,默认返回这个对象
     new foo()//函数可以作为一个类
    

2.2 apply/call/bind

  • call
    • 参数
      • 第一个参数绑定 this
      • 后续参数作为实参传入函数,参数列表
  • apply
    • 参数
      • 第一个参数绑定 this
      • 第二个参数(传入真正的实参),以数组的形式写,会将每一个参数赋值给形参
    // call/apply
    function foo(name, age, height) {
      console.log("foo函数被调用:", this)
      console.log("打印参数:", name, age, height)
    }

    // ()调用
    // foo("whf", 18, 1.88)

    // apply
    // 第一个参数: 绑定this
    // 第二个参数: 传入额外的实参, 以数组的形式
    // foo.apply("apply", ["kobe", 30, 1.98])

    // call
    // 第一个参数: 绑定this
    // 参数列表: 后续的参数以多参数的形式传递, 会作为实参
    foo.call("call", "james", 25, 2.05)
  • 两者区别

    • 都可以调起函数,都可以绑定一个 this
    • 绑定实参的时候格式不一样
  • bind():创建一个绑定函数BF,比较少用

    • 绑定函数是一个怪异函数对象(看起来是一个独立的函数,但是已经绑定好了 this )
    • 其他参数:参数也可以绑定
    • 代码阅读性放在第一位
    function foo(){
        console.log("foo:",this);
    }
    var obj={name:"whuuuy"}
        // 需求:调用 foo 时,总是绑定到obj对象,又不需要obj对象身上有函数
        foo.apply(obj)//每次都需要apply(obj)
        // bind函数登场:
        var bar=foo.bind(obj)//绑定obj对象
        bar()//依然指向foo函数,但是foo中的this绑定了obj
    
    
    • 其他参数
        // bind函数的其他参数(了解)
        var bar = foo.bind(obj, "kobe", 18, 1.88)
        bar("james")//会传到最后一个
    

2.3 内置函数的调用绑定

  • 根据经验
  • 框架可能会说
  <button>按钮</button>

  <script>
    // 内置函数(第三方库): 根据一些经验
    // 1.定时器 window
    // setTimeout(function() {
    //   console.log("定时器函数:", this)
    // }, 1000)

    // 2.按钮的点击监听
    // var btnEl = document.querySelector("button")
    // btnEl.onclick = function() {
    //   console.log("btn的点击:", this)
    // }

    // btnEl.addEventListener("click", function() {
    //   console.log("btn的点击:", this)
    // }) //btnEl

  //  3.forEach window
    // 第一个参数:回调函数 
    // 第二个参数:绑定 this
    var names = ["abc", "cba", "nba"]
    names.forEach(function(item) {
      console.log("forEach:", this)
    }, "aaaa")

2.4 规则优先级

  • 默认绑定(独立函数调用)的优先级最低
  • 显式绑定的优先级高于隐式绑定
  • new 绑定优先级高于隐式绑定
    • new 不能 和 apply、call 一起使用,没有可比性
    • new 的优先级高于 bind 优先级
    • bind 的优先级高于 apply,call
      • 但是 bind 很少用
  • new ( 新对象 ) > bind > apply、call > 隐式绑定 > 默认绑定
    // function foo() {
    //   console.log("foo:", this)
    // }

    // 比较优先级:

    // 1.显式绑定绑定的优先级高于隐式绑定
    // 1.1.测试一:apply 高于隐式绑定
    // var obj = { foo: foo }
    // obj.foo.apply("abc")
    // obj.foo.call("abc")

    // 1.2.测试二:bind 高于隐式绑定
    // var bar = foo.bind("aaa")
    // var obj = {
    //   name: "iii",
    //   baz: bar
    // }
    // obj.baz() //aaa


    // 2.new 绑定优先级高于隐式绑定
    // var obj = {
    //   name: "whkkkkpy",
    //   foo: function() {
    //     console.log("foo:", this)
    //     console.log("foo:", this === obj)
    //   }
    // }
    // new obj.foo() 指向空对象


    // 3.new/显式
    // 3.1. new不可以和apply/call一起使用

    // 3.2. new优先级高于bind
    // function foo() {
    //   console.log("foo:", this)
    // }
    // var bindFn = foo.bind("aaa")
    // new bindFn() //空对象


    // 4.bind/apply优先级
    // bind优先级高于apply/call
    function foo() {
      console.log("foo:", this)
    }
    var bindFn = foo.bind("aaa")
    bindFn.call("bbb")

2.5 this规则之外

  • 显式绑定传入 null/undefined , 那么使用的规则是默认绑定
  • 间接函数引用,使用默认绑定规则
    // 1.情况一: 显式绑定null/undefined, 那么使用的规则是默认绑定
    // function foo() {
    //   console.log("foo:", this)
    // }

    // foo.apply("abc")
    // foo.apply(null) window
    // foo.apply(undefined) window


    // 2.情况二: 间接函数引用
    var obj1 = {
      name: "obj1",
      foo: function () {
        console.log("foo:", this)
      }
    }
    var obj2 = {
      name: "obj2"
    };
    // ()小括号:1. 当成整体 2. 函数调用

    // {}[]()前要加;分号

    // obj2.foo = obj1.foo
    // obj2.foo()
    // 开发中不能这么写
    (obj2.foo = obj1.foo)()//独立函数调用
  • 箭头函数

2.6 箭头函数

  • 箭头函数不会绑定 this、arguments 属性
  • 箭头函数不能作为构造函数来使用(不能和 new 一起来使用,会抛出错误),没有原型
    // 1.之前的方式
    function foo1() { }
    var foo2 = function (name, age) {
      console.log("函数体代码", this, arguments)
      console.log(name, age)
    }

    // 2.箭头函数完整写法
    var foo3 = (name, age) => {
      console.log("箭头函数的函数体")
      console.log(name, age)
    }

    // 3.箭头函数的练习
    // 3.1. forEach
    var names = ["abc", "cba", "nba"]
    names.forEach(function (item) {
      console.log(item);
    })
    names.forEach((item, index, arr) => {
      console.log(item, index, arr)
    })
    // 3.2. setTimeout
    setTimeout(function () {
      console.log("settimeout");
    }, 3000)
    setTimeout(() => {
      console.log("setTimeout")
    }, 3000)

  • 编写优化

    • 如果只有一个参数 ,() 可以省略

    • 如果函数体中只有一行执行代码,{}可以省略

    • 一行代码中不能带return关键字,如果省略{},return一起省

    • 并且这行代码的表达式结果返回值会作为整个函数的返回值

    • 只有一行代码时候,运行代码的表达式结果会作为函数的返回值默认返回

    • 如果默认返回值是一个对象,那么这个对象必须加**({对象})**

    var names = ["abc", "cba", "nba"]
    var nums = [20, 30, 11, 15, 111]

    // 1.优化一: 如果箭头函数只有一个参数, 那么()可以省略
    // names.forEach(item => {
    //   console.log(item)
    // })
    // var newNums = nums.filter(item => {
    //   return item % 2 === 0
    // })

    // 2.优化二: 如果函数体中只有一行执行代码, 那么{}可以省略
    // names.forEach(item => console.log(item))

    // 一行代码中不能带return关键字, 如果省略{}, 需要带return一起省略(下一条规则)
    // var newNums = nums.filter(item => {
    //   return item % 2 === 0
    // })

    // 3.优化三: 只有一行代码时, 这行代码的表达式结果会作为函数的返回值默认返回的
    // var newNums = nums.filter(item => item % 2 === 0)
    // var newNums = nums.filter(item => item % 2 === 0)


    // 4.优化四: 如果默认返回值是一个对象, 那么这个对象必须加()
    // 注意: 在react中会经常使用 redux

    // var arrFn = () => ["abc", "cba"]
    // var arrFn = () => {} // 注意: 这里是{}执行体
    // var arrFn = () => {name: "iii" } 
i
    // var arrFn = () => ({ name: "iii" })
    // console.log(arrFn())

    // 箭头函数实现nums的所有偶数平方的和
    var nums = [20, 30, 11, 15, 111]
    var result = nums.filter(item => item % 2 === 0)
                     .map(item => item * item)
                     .reduce((prevValue, item) => prevValue + item)
    console.log(result)
  • this规则之外, 箭头函数不绑定 this ,遵循变量查找规则,在作用域(函数)中找
    • 也不能通过 apply、call 绑定去找
    • 一层一层往上面查找
    • 对象的** {} 不是作用域,只有函数作用域全局作用域**
    // 1.普通函数中是有this的标识符
    // function foo() { 


    //   console.log("foo", this)


    // }

    // foo()
    // foo.apply("aaa")

    // 2.箭头函数中, 压根没有this
    // var bar = () => {
    //   console.log("bar:", this)
    // }
    // bar() window
    // 通过apply调用时, 也是没有this,还是从上层作用域中找
    // bar.apply("aaaa")

    // console.log("全局this:", this)
    // var message = "global message"

    // 3.this的查找规则
        var obj = {
      name: "obj",
      foo: function(){   


        var bar = () => {
          console.log("bar:", this)
        }
        return bar
        

      }
    } 
    var fn = obj.foo() //fn 指向 bar 函数
    fn.apply("bbb") //没有this,绑过来有什么用,要this?去上层作用域,foo函数,谁调用啊?obj!!!

    // var obj = {
    //   name: "obj",
    //   foo: () => {
    //     var bar = () => {
    //       console.log("bar:", this)
    //     }
    //     return bar
        
    //   }
    // }
    // var fn = obj.foo()
    // fn.apply("bbb") window 

  • 箭头函数中 this 应用场景:网络请求
    • 异步:代码不会按照顺序一点一点执行
    // 网络请求的工具函数

    function request(url, callbackFn) {
      var results = ["abc", "gds", "sws"]
      callbackFn(results)//在这里调用默认是this
    }
    // 实际操作的位置
    var obj = {
      name: [],
      network: function () {
        // // 1. 早期
        // var _this=this
        // request("/songs/love",function(res){
        //   // console.log(res);//拿到results放在name里面
        //   _this.name=[].concat(res)


        // })
        // 2. 箭头函数
        request("/name", (res) => {
          this.name = [].concat(res)
        })
      }
    }

    obj.network()
 console.log(obj.name);

三、题海战术(持续更新)

3.1 题目

var name = "window";

var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};

function sayName() {
  var sss = person.sayName;

  sss(); // 绑定: 默认绑定, window -> window

  person.sayName(); // 绑定: 隐式绑定, person -> person

  (person.sayName)(); // 虽然加了小括号,但是什么都没有做,标识符的优先级并没有改变。点语法的优先级很高。绑定: 隐式绑定, person -> person

  (b = person.sayName)(); // 术语: 间接函数引用,得到独立的函数,进行调用 window -> window
}

sayName();
var name = 'window'


// {} -> 对象
// {} -> 代码块
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  // 箭头函数不绑定 this
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    // console.log(this) // 第一个表达式this -> person1
    // console.log(this) // 第二个表达式this -> person2
    // console.log(this) // 第三个表达式this -> person1
    
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

// 作用域:函数 和 全局
// 开始题目:
person1.foo1(); // 隐式绑定: person1
person1.foo1.call(person2); // 显式绑定: person2
// 箭头函数隐式绑定没有意义,上层作用域
person1.foo2(); // 上层作用域: window
// 也不能使用 call 进行调用
person1.foo2.call(person2); // 上层作用域: window

// 拿到这个函数直接进行调用
person1.foo3()(); // 默认绑定: window
// 显式绑定到 person2进行调用,返回值是一个函数独立调用
person1.foo3.call(person2)(); // 默认绑定: window
// 拿到返回值显式绑定 person2
person1.foo3().call(person2); // 显式绑定: person2
// 隐式绑定person1,箭头函数去上层函数找,也是person1
person1.foo4()(); // person1
// 显式绑定 person2,箭头函数去上层作用域找,也是 person2
person1.foo4.call(person2)(); // person2
// 隐式绑定person1,箭头函数不能通过 call 去找
person1.foo4().call(person2); // person1
var name = 'window'

/*
  1.创建一个空的对象
  2.将这个空的对象赋值给this
  3.执行函数体中代码
  4.将这个新的对象默认返回
*/
function Person(name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}

// person1/person都是对象(实例instance)
var person1 = new Person('person1')
var person2 = new Person('person2')


// 面试题目:
person1.foo1() // 隐式绑定: person1
person1.foo1.call(person2) // 显式绑定: person2

// 箭头函数都是去上层作用域找
person1.foo2() // 上层作用域查找: person1
person1.foo2.call(person2) // 上层作用域查找: person1
// 返回一个函数直接进行调用
person1.foo3()() // 默认绑定: window
// foo3是person2进行调用,但是返回的是一个普通函数,还是进行默认调用的
person1.foo3.call(person2)() // 默认绑定: window
// 拿到返回值进行显式绑定
person1.foo3().call(person2) // 显式绑定: person2

person1.foo4()() // 上层作用域查找: person1(隐式绑定)

person1.foo4.call(person2)() //  上层作用域查找: person2(显式绑定)
// 箭头函数不能通过 call 进行调用
person1.foo4().call(person2) // 上层作用域查找: person1(隐式绑定)
var name = 'window'
// 通过 new 进行调用
/*
  1.创建一个空的对象
  2.将这个空的对象赋值给 this (对this进行操作就是在操作这个对象)
  3.执行函数体中代码
  4.将这个新的对象默认返回
*/
function Person(name) {
  this.name = name
  // person 里面放了一个 obj 对象
  this.obj = {
    // obj 对象指向一个新的对象
    name: 'obj',
    // 又生成一个新的函数对象
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}

var person1 = new Person('person1')
var person2 = new Person('person2')
// 返回新的函数直接进行调用
person1.obj.foo1()() // 默认绑定: window
// 也是返回新的函数直接进行调用,知道如何被调用起来的
person1.obj.foo1.call(person2)() // 默认绑定: window
// 返回新的函数显式绑定 person2
person1.obj.foo1().call(person2) // 显式绑定: person2
// 返回的是箭头函数,foo2是通过obj进行调用的
person1.obj.foo2()() // 上层作用域查找: obj(隐式绑定)
// 返回的是箭头函数,foo2 是通过显式绑定的
person1.obj.foo2.call(person2)() // 上层作用域查找: person2(显式绑定)
// 箭头函数通过 call 调用没有意义
person1.obj.foo2().call(person2) // 上层作用域查找: obj(隐式绑定)

3.2 总结

  • 显式绑定传入 null/undefined , 那么使用的规则是默认绑定
  • 默认绑定 this 指向调用的对象
  • 立即执行匿名函数有自己的作用域,变量没找到会沿着作用域链往上查找,this 指向全局对象
  • 重点:看谁调用,不要和变量查找题搞混了
  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值