JS高级

65 阅读5分钟

一. 函数中的this指向

function foo() {
  console.log(this)
}

// 1.调用方式一: 直接调用
foo() //this指向window

// 2.调用方式二: 将foo放到一个对象中再调用
var obj = {
   var name: "zxx",
   var age: 22,
   foo: foo
}
obj.foo() //this指向obj对象

// 3.调用方式三: 通过call/apply调用
foo.call("abc") //this指向String{"abc"}对象

这个案例可以给到我们什么样的启示呢?
    1.函数在调用时,JS会默认给this绑定一个值
    2.this的绑定和定义位置(编写的位置)没有关系
    3.this的绑定和调用方式以及调用的位置有关系
    4.this是在运行时被绑定的

1. 默认绑定

什么情况下使用默认绑定?: 独立函数调用
    独立的函数调用可以理解成函数没有被绑定到某个对象上进行调用
// 1.普通的函数被独立的调用
function foo() {
    console.log("foo function")
}

foo() //this指向window

//2.函数定义在对象内,但是也被独立调用
var obj = {
    name: "zxx",
    bar: function() {
        console.log("bar", this)
    }
}

var barFn = obj.bar
barFn() //this指向window

//3.高阶函数
function baz(fn) {
    fn() //this指向window
}

baz(foo)

//4.严格模式下,独立调用的函数中的this指向的是undefined

2.隐式绑定

隐式绑定: 通过某个对象发起的函数调用
隐式绑定有一个前提条件:
必须在调用的对象内部有一个对函数的引用(比如一个属性)
如果没有这样的引用,在进行调用时会报找不到该函数的错误
正是通过这个引用,间接的将thos绑定到了这个对象上
function foo() {
    console.log("foo",this)
}

//1.
var obj = {
    name: "zxx",
    foo: foo
}

obj.foo() //this指向obj对象

//2. 
var obj2 = {
    name: "obj2",
    foo: foo
}

var obj3 = {
    name: "obj3",
    bar: obj2
}

obj3.bar.foo() //this指向obj2

//3.
var obj = {
    name: "obj",
    foo: foo
}

var bar = obj.foo
bar() //this指向window

3.new绑定this

JS中的函数可以当做一个类的构造函数来使用,也就是new关键字
使用new关键字来调用函数时会执行如下操作
    1.创建一个全新的空对象
    2.这个新对象会被执行prototype连接
    3.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成)
    4.如果函数没有返回其他对象,会默认返回这个新对象
function Person(name) {
   console.log(this) Person{}
   this.name = name Person {name: "why"}
}

var p = new Person("zxx")
console.log(p)

4.显示绑定

如果不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,可以使用显式绑定
JS中所有的函数都可以使用call和apply方法
第一个参数是相同的,要求传入一个对象, 这个对象的作用就是给this准备的,在调用这个函数时,会将
this绑定到传入的这个对象上
后面的参数: apply为数组,call为参数列表
    fn.call(thisArg, [argsArray])
    fn.apply(thisArg, arg1, arg2...)
    
function foo() {
   console.log("foo function", this)
}

var obj = {
    name: "zxx"
}

//执行函数,并且让函数中的this指向obj对象
obj.foo = foo
obj.foo() //隐式绑定 this指向obj对象

//执行函数,并且强制函数里的this指向obj对象
foo.call(obj) //this指向obj对象
foo.apply(obj) //this指向obj对象
上面的过程,明确的绑定了this指向的对象,所以称之为显式绑定

5.call/apply/bind的额外补充

function foo(name, age, height) {
   console.log("foo函数被调用", this)
   console.log("name", name, "age", age, "height", height)
}

//独立调用
// foo("zxx", 18, 1.88) //this指向window

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

//call
//第一个参数: 绑定this
//第二个参数: 参数列表,后续的参数以多参数的形式传递,回作为实参
foo.call("james", 20, 1.99)

//bind: 需求: 当调用函数时,希望将函数绑定到obj对象身上,但不希望obj对象身上有foo函数
var obj = {
   name: "zxx"
}

//1.bind函数的基本使用
var bar = foo.bind(obj)
bar() this指向obj

6.内置函数的调用this绑定


  <button class="btn">按钮</button>

    // 1.定时器
    setTimeout(function() {
      console.log(this) //window
    }, 1000) 

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

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

    // 3.forEach
    var names = ["abc", "cba", "nba"]
    names.forEach(function() {
      console.log("forEach", this)
    }, "zxx")

7.this绑定的优先级比较

 // 比较优先级:
    /*
      new 
      bind
      apply/call
      隐式绑定
      默认绑定
    */

    // 1.显示绑定的优先级高于隐式绑定
    // 1.1测试一: apply高于默认绑定
    function test1() {
      console.log("test1", this) //this指向zxx
                                 //this指向abc
    }

    var obj = { foo: test1 }
    obj.foo.apply("zxx")
    obj.foo.call("abc")
    
    // 1.2测试二: bind优先级高于默认绑定
    function test2() {
      console.log("test2", this) //this指向aaa
    }
    
    var bar1 = test2.bind("aaa")
    var obj = {
      name: "zxx",
      bar: bar1
    }

    obj.bar()

    // 2.new绑定优先级高于隐式绑定
    function test3() {
      console.log("test3", this)
    }

    var obj = {
      name: "zxx",
      test3: function() {
        console.log("test3",this) //new obj.foo(): this指向test3{}空对象
        console.log("test3",this === obj) //false
      }
    }

    new obj.test3()
    
    // 3.new绑定优先级高于显式绑定
    // 3.1 new不可以和apply/call一起使用

    // 3.2new优先级高于bind
    function test4() {
      console.log("test4", this) // bindFn():this指向ccc
                                 // new bindFn: this指向test4{} 空对象
    }

    var bindFn = test4.bind("ccc")
    bindFn()

    new bindFn

    // 4.bind优先级高于apply/call
    function test5() {
      console.log("test5", this) //bindFn2(): this指向ddd
                                 //bindFn2.call("hhh"): this指向ddd
                                 //bindFn2.apply("kkk"): this指向ddd
    }

    var bindFn2 = test5.bind("ddd")
    bindFn2()
    bindFn2.call("hhh")
    bindFn2.apply("kkk")

8.this几种绑定规则之外的情况

 // 1.情况一: 显式的绑定null或undefined,那么使用的规则是默认绑定
    function foo() {
      console.log("foo",this)
    }

    foo.apply("aaa") //this指向aaa
    foo.apply(null) //this指向window
    foo.apply(undefined) //this指向window

    // 2.情况二: 间接函数引用
    var obj1 = {
      name: "obj1",
      foo: function() {
        console.log("foo",this)
      }
    }

    var obj2 = {
      name: "obj2"
    };

    // obj2.foo = obj1.foo
    // obj2.foo()

    // 下面这种写法也是默认绑定
    (obj2.foo = obj1.foo)() //this指向window

9.this面试题

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

function sayName() {
  var sss = person.sayName
  sss(); //独立调用: this指向window 打印window
  person.sayName(); //隐式绑定: this指向person 打印person
  (person.sayName)(); //隐式绑定: this指向person 打印person
  (b = person.sayName)(); //间接函数引用: 这个表达式默认会返回函数,this指向window 打印window
}

sayName()
2.
var name = "window"
var person1 = {
    name: "person1",
    foo1: function() {
        console.log(this.name)
    },
    foo2: () => console.log(this.name),
    foo3: function() {
        return function() {
            console.log(this.name)
        }
    },
    foo4: function() {
        return () => {
            console.log(this.name)
        }
    }
}

var person2 = { name: "person2" }

person1.foo1() //隐式绑定,this指向person1,打印person1
person1.foo1.call(person2) //显示绑定,this指向person2,打印person2

person1.foo2() //默认绑定,this指向window,打印window
person1.foo2.call(person2) //默认绑定,this指向window,打印window

person1.foo3()() //默认绑定,this指向window,打印window
person1.foo3.call(person2)() //默认绑定,window
perosn1.foo3().call(person2) //显示绑定,person2

person1.foo4()() //window
person1.foo4().call(person2)() //window
person1.foo4().call(person2) //window