JS - this指向

187 阅读4分钟

this

  this在JS中指的是当前对象的一个应用。只有在函数执行的时候才有意义(产生),每个函数在执行的时候都有自己的this指向。简而言之,谁调用该函数,该函数的this指向就是谁。

this指向的四种绑定规则

  • 默认绑定规则
    默认指向全局对象GO(浏览器环境中默认指向window)。
  • 隐式绑定规则
    谁调用就只指向谁(隐式丢失,参数赋值)。
  • 显式绑定规则
    call、apply、bind三个方法手动绑定this指向。
  • new绑定规则
    new一个构造函数之后,构造函数内的this指向是new出来的实例对象。

1. 默认绑定规则

  对函数进行独立调用时,此时没有使用其他绑定规则绑定this指向,this默认指向GOGO全局对象,浏览器环境下全局对象为windowwindow

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

fn(); //函数独立调用,输出 window

2. 隐式绑定规则

  使用函数时,用一个对象调用该函数,那么函数的this指向就是这个对象。

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

var obj = {};

obj.fn(); //输出 obj

  隐式绑定还会存在一个特殊现象:隐式丢失。

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

var obj = {};
var fn2 = obj.fn;
fn2(); //隐式丢失,输出 window

3. 显式绑定规则

  使用call、apply、bind对函数进行手动绑定this指向。

function fn(){
    console.log(this);
}
var obj = {};
fn.call(obj); //输出obj
fn.apply(obj); //输出obj
fn.bind(obj)(); //输出obj

  call、apply、bind 的异同点

  • call和apply的使用方法相似,调用就执行一次函数;bind是返回一个绑定了指定this对象的新函数,需要手动调用。
  • call和apply使用时,函数的参数也要和指定的this对象一起进行传参,call的参数需要依次输入,apply的参数是作为一个数组进行传参;由bind返回的是新函数,函数传参与普通函数执行一致。
function fn(a,b){
    console.log(this , a + b);
}
var obj = {};
fn.call(obj,2,3); //输出 obj 2 3
fn.apply(obj,[5,6]) //输出 obj 5 6

const fn2 = fn.bind(obj);
fn(2,4); //输出 window 2 4
fn2(2,4): //输出 obj 2 4

PS:如果绑定的this对象为undefined或null,则为无效绑定,使用默认绑定规则。即,this指向window。

4. new绑定规则

  使用new关键字实例化一个函数时,函数的this指向就是实例化出来的对象。

function fn(){
    this.a = 3;
}
const obj = new fn();
console.log(obj.a); //输出 3

箭头函数

  箭头函数是一种特殊的函数定义方式,特点是函数本身没有this,箭头函数的this其实是父作用域的this指向,简而言之,谁定义了箭头函数,箭头函数的this就指向谁。又因为箭头函数没有this,所以上述的四种绑定规则都不适用于箭头函数。

const fn = ()=>{
    console.log(this);
}

function fn2(){
    return ()=>{
        console.log(whis);
    }
}

const obj = {
    foo: ()=>{
        console.log(this);
    }
};

fn(); // 输出 window 在全局作用域下定义的
obj.foo(); // 输出 window 在全局作用域下定义的,隐式绑定无效
fn.call(obj); // 输出 window ,显式绑定无效
new fn()l=; //报错, 箭头函数不允许作为构造函数使用,不能使用new关键字

const fn3 = obj.fun2();
fn3(); // 输出obj,如果fn2返回的不是箭头函数此处返回window,默认规则失效,fn3是在obj下定义的。

练手

  • 例题1
var name = "window";
var obj1 = {
  name: "1",
  fn1: function () {
    console.log(this.name);
  },
  fn2: () => console.log(this.name),
  fn3: function () {
    return function () {
      console.log(this.name);
    };
  },
  fn4: function () {
    return () => console.log(this.name);
  },
};
var obj2 = {
  name: "2",
};

obj1.fn1();
obj1.fn1.call(obj2);

obj1.fn2();
obj1.fn2.call(obj2);

obj1.fn3()();
obj1.fn3().call(obj2);
obj1.fn3.call(obj2)();

obj1.fn4()();
obj1.fn4().call(obj2);
obj1.fn4.call(obj2)();

点击查看答案
obj1.fn1(); // 1 隐式绑定
obj1.fn1.call(obj2); // 2 显式绑定

obj1.fn2(); // window 箭头函数,隐式失效
obj1.fn2.call(obj2); // windown 箭头函数,显式失效

obj1.fn3()(); // window 默认绑定,函数独立调用
obj1.fn3().call(obj2); // 2 显式绑定
obj1.fn3.call(obj2)(); // window 默认绑定,函数独立调用

obj1.fn4()(); // 1 箭头函数 fn4的this指向obj1
obj1.fn4().call(obj2); // 1 显式失效 fn4的this指向obj1
obj1.fn4.call(obj2)(); // 2 fn4的this指向obj2
  • 例题2
function Foo() {
  getName = function () {
    console.log(1);
  };
  return this;
}

Foo.getName = function () {
  console.log(2);
};
Foo.prototype.getName = function () {
  console.log(3);
};
var getName = function () {
  console.log(4);
};
function getName() {
  console.log(5);
}

Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

点击查看答案
Foo.getName(); //2 这里的getName是Foo的getName属性。
getName(); // 4 window的getName,先声明后赋值,声明时是5,赋值以后为4。
Foo().getName(); //1 返回的this为window,由于在Foo()里面赋值给了未声明的变量getName,此getName直接赋予window。
getName(); //1 在上一步被赋值。
new Foo.getName(); //2  等同new (Foo.getName)(),实例化的是Foo的getName属性。
new Foo().getName(); //3 等同 (new Foo()).getName(),实例化Foo,得到的getName是Foo原型链上的getName。
new new Foo().getName(); //3 等同new((new Foo()).getName)(), 里层的new得到的还是Foo原型链上的getName,外层的new实例化了里层的getName,故输出 3。