js的this揭秘

86 阅读6分钟

js的this揭秘

this在全局作用域的指向

// this一般出现在函数中

// 全局作用域中:
// 浏览器: this就是window
// Node环境: this为{}
console.log(this);
// console.log(window); // Node里面没有window对象


/**
 * 关于Node中this为{}的解释:
 * node会将每一个js文件当作一个module模块
 * module->加载->编译->放在一个函数中->执行这个函数->.call()绑定this.(node模板)
 * this在绑定之前就是一个{}.
 */
 

调用同一个函数this的差别

// this的指向, 跟函数所处的位置是没有关系的
// 跟函数被调用者有关系 (被谁调用)

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

// 1.直接调用这个函数
foo() 

// 2.创建一个对象, 对象中的函数指向foo
var obj = {
  name: 'Fhup',
  foo: foo
}

obj.foo()

// 3.apply调用
foo.apply("hao")

四种绑定规则

绑定规则一-默认绑定

// 默认绑定: 独立函数调用. this就是window  注意: 有没有调用主题,没有就是window


// 案例1
// function foo1() {
//   console.log(this)
// }

// function foo2() {
//   console.log(this)
//   foo1()
// }

// function foo3() {
//   console.log(this)
//   foo2()
// }

// foo3()


// 案例2
// var obj = {
//   name: "why",
//   foo: function() {
//     console.log(this)
//   }
// }

// var bar = obj.foo
// bar() // window


// 案例3
// function foo() {
//   console.log(this)
// }
// var obj = {
//   name: "why",
//   foo: foo
// }

// var bar = obj.foo
// bar() // window

// 5.案例五:
function foo() {
  function bar() {
    console.log(this)
  }
  return bar
}

var fn = foo()
fn() // window

// 以上都是独立函数调用

绑定规则二-隐式绑定

// 隐式绑定: object.fn()
// object对象会被js引擎绑定到fn函数中的this里面

// 案例1
// var obj = {
//   name: "why",
//   foo: foo
// }

// obj.foo() // obj对象

// 案例2
// var obj = {
//   name: "why",
//   eating: function() {
//     console.log(this.name + "在吃东西")
//   },
//   running: function() {
//     console.log(obj.name + "在跑步")
//   }
// }

// obj.eating()
// obj.running()

// var fn = obj.eating
// fn()  // window


// 案例3
var obj1 = {
  name: "obj1",
  foo: function() {
    console.log(this)
  }
}

var obj2 = {
  name: "obj2",
  bar: obj1.foo
}

obj2.bar()

绑定规则三-显式绑定-apply-call-bind

// function foo() {
//   console.log("函数被调用了", this)
// }

// 1.foo的直接调用和call/apply调用的不同在于this绑定的不同
// foo直接调用指向的是全局对象(window)
// foo()

// var obj = {
//   name: "obj"
// }

// call/apply是可以指定this的绑定对象
// foo.call(obj)
// foo.apply(obj)
// foo.apply("aaaa")


// 2.call和apply有什么区别?
function sum(num1, num2, num3) {
  console.log(num1 + num2 + num3, this)
}

// 传参方式不同
sum.call("call", 20, 30, 40)
sum.apply("apply", [20, 30, 40]) // 剩余参数放在数组中

// call和apply在执行函数时,可以明确的绑定this, 这个绑定规则称之为显示绑定

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

// 默认绑定和显示绑定bind冲突: 优先级(显示绑定)
var newFoo = foo.bind("aaa") // 绑定完,生成新的函数.
newFoo() // 显式绑定>默认(独立函数) aaa



newFoo()
newFoo()

绑定规则四-new绑定

// 我们通过一个new关键字调用一个函数时, 这个时候this是在调用这个函数构造器时创建出来的对象
// this = 创建出来的对象, this绑定在创建出来的对象上
// 这个绑定过程就是 new绑定
// new Person() 执行时,把函数当作构造器.就会执行一次函数

function Person(name, age) {
  this.name = name
  this.age = age
}

var p1 = new Person("why", 18)
console.log(p1.name, p1.age)

var p2 = new Person("kobe", 30)
console.log(p2.name, p2.age)

规则优先级

/**
 * 默认绑定的优先级 最低,独立函数调用
 * 显式绑定大于隐式绑定     call/apply/bind > obj.fn()
 *                        bind优先级 > call/apply
 * new绑定大于隐式绑定   new > obj.fn()
 * new绑定的优先级最高 > 显式绑定     结论: new关键字和apply/call不能一起使用
 * 
 * 
 * new绑定 > 显示绑定(apply/call/bind) > 隐式绑定(obj.fn()) > 默认绑定(独立函数调用)
 */

二个特殊的优先级

忽略显式绑定-特殊

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

foo.apply('ccc') // ccc
foo.apply({}) // {}

// apply/call/bind: 传入null/undefined时,自动将this绑定为全局对象window
foo.apply(null) // window
foo.apply(undefined) // window

var fn = foo.bind(null)
fn() // 显式>默认 window

间接函数引用-特殊

var obj1 = {
  foo: function foo(){
    console.log(this);
  }
}

var obj2 = {
  name:'Fhup'
}; // 注意: 加上 ; ,特殊的写法执行时会在一行

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

// (obj2.fn = obj1.foo)()  括号里面的当作整体,返回foo,进行执行
(obj2.fn = obj1.foo)()   // window 这种相当于独立函数调用(但不是独立函数调用)

箭头函数的this

/**
 * 箭头函数是ES6的新方法
 * 1.箭头函数不会绑定this.arauments
 * 2.不能当做构造器来使用(不能与new同时使用)
 */

var foo = (n1, n2) => {
  return n1 + n2
}

foo(1, 2)

/**
 * 简写:
 * 1. 一个参数 ()省略
 * 2. 一行代码 {}省略  并且将这一行代码的结果作为返回值返回
 * 3. 返回值为对象时 加上() 
 */

var bar = () => {
  return { name: 'Fhup', age: 18 }
}

// 返回值为对象时 加上()
var xxx = ()=>({ name: 'Fhup', age: 18 })

// 箭头函数不绑定this,去上层作用域里面找
// 结论: 定义的对象不产生作用域,所以foo2的上层作用域是全局
//      定义的函数产生作用域,去当前函数外的对象里面找

// 1.测试箭头函数中this指向
var name = "why"

var foo = () => {
  console.log(this)
}

foo() // this为 window
var obj = { foo: foo }
obj.foo() // this为 window
foo.call("abc") // this为 window

// 2.应用场景
var obj = {
  data: [],
  getData: function() {
    // 发送网络请求, 将结果放到上面data属性中
    // 在箭头函数之前的解决方案
    // var _this = this
    // setTimeout(function() {
    //   var result = ["abc", "cba", "nba"]
    //   _this.data = result
    // }, 1000);
    // 箭头函数之后
    setTimeout(() => {
      var result = ["abc", "cba", "nba"]
      this.data = result
      console.log(this.data);
    }, 1000);
  }
}

obj.getData()

this面试题

var name = "window";

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

function sayName() {
  var sss = person.sayName;
  sss(); // window: 独立函数调用
  person.sayName(); // person: 隐式调用
  (person.sayName)(); // person: 隐式调用
  (b = person.sayName)(); // window: 赋值表达式(独立函数调用)  前面括号返回sayName进行执行
  console.log(this.name); // window
}

sayName();

var name = 'window'
// 定义对象不产生作用域,所以foo2的上层作用域是全局
// 如果上层作用域为函数,看当前是谁调用的,或者this是定义的对象
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(); // person1(隐式绑定)
// person1.foo1.call(person2); // person2(显示绑定优先级大于隐式绑定)

// person1.foo2(); // window(不绑定作用域,上层作用域是全局)
// person1.foo2.call(person2); // window

// person1.foo3()(); // window(独立函数调用)  person1.foo3()是返回的结果,没有调用主题
// person1.foo3.call(person2)(); // window(独立函数调用)
// person1.foo3().call(person2); // person2(最终调用返回函数式, 使用的是显示绑定)


person1.foo4()(); // person1 (箭头函数不绑定this, 上层作用域是foo4()的this是person1)
person1.foo4.call(person2)(); // person2(上层作用域被显示的绑定了一个person2)
person1.foo4().call(person2); // person1  (箭头函数不绑定this, 上层作用域是foo4()的this是person1)

var name = 'window'

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)
    }
  }
}

var person1 = new Person('person1')
var person2 = new Person('person2')

// person1.foo1() // person1 (隐式调用)
// person1.foo1.call(person2) // person2(显示高于隐式绑定)

// person1.foo2() // person1 (上层作用域中的this是person1)
// person1.foo2.call(person2) // person1 (上层作用域中的this是person1)

// person1.foo3()() // window(独立函数调用)
// person1.foo3.call(person2)() // window
// person1.foo3().call(person2) // person2

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


var name = 'window'

function Person (name) {
  this.name = name
  
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      // foo2被obj调用.this就是obj
      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
// person1.obj.foo1().call(person2) // person2


person1.obj.foo2()() // obj (箭头函数不绑定this, 上层作用域是foo2()的this是obj)
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj (箭头函数不绑定this, 上层作用域是foo2()的this是obj)




var obj = {  //  { } 是个对象,不包含作用域
  name: "obj",
  foo: function() {
    // 上层作用域是全局
  }
}

// 函数有作用域
function Student() {
  // Student上层作用域为全局
  this.foo = function() {
    // 上层作用域是Student
  }
}

var obj = { // 定义对象不产生作用域
  name: "obj",
  foo: function() { // foo的上层作用域为全局
    return ()=>{ //箭头函数的this就是foo的this.
      console.log(this.name); // name
    }
  }
}