this指向复习

this指向

this 的指向,是在调用函数时根据执行上下文所动态确定的。

  • 在函数体中,简单调用该函数时(非显式/隐式绑定下),严格模式下 this 绑定到 undefined,否则绑定到全局对象 windowglobal

  • 一般构造函数 new 调用,绑定到新创建的对象上;

  • 一般由 call/apply/bind 方法显式调用,绑定到指定参数的对象上;

  • 一般由上下文对象调用,绑定在该对象上;

  • 箭头函数中,根据外层上下文绑定的 this 决定 this 指向。

例1:

function f1() {
  console.log(this);
}
function f2() {
  "use strict";
  console.log(this);
}
f1(); //window
f2(); //undefined

函数在浏览器全局环境中被简单调用,非严格模式下 this 指向 window;在 use strict 指明严格模式的情况下就是 undefined

例2:

const foo = {
  bar: 10,
  fn: function () {
    console.log(this);
    console.log(this.bar);
  },
};
var fn1 = foo.fn;
fn1(); // window undefined

这里 this 仍然指向的是 window。虽然 fn 函数在 foo 对象中作为方法被引用,但是在赋值给 fn1 之后,fn1 的执行仍然是在 window 的全局环境中。

例3:

const foo = {
  bar: 10,
  fn: function () {
    console.log(this);
    console.log(this.bar);
  },
};
foo.fn();

//{bar: 10, fn: ƒ}
//10

foo.fn() 语句中 this 指向 foo 对象。

在执行函数时,如果函数中的 this 是被上一级的对象所调用,那么 this 指向的就是上一级的对象;否则指向全局环境。

例4:

const student = {
  name: "xxx",
  fn: function () {
    return this;
  },
};
console.log(student.fn() === student); // true

例5:

const person = {
  name: "person",
  student: {
    name: "student",
    fn: function () {
      return this.name;
    },
  },
};
console.log(person.student.fn()); // student

在这种嵌套的关系中,this 指向最后调用它的对象,因此输出将会是:student

例6:

const o1 = {
  text: "o1",
  fn: function () {
    return this.text;
  },
};
const o2 = {
  text: "o2",
  fn: function () {
    return o1.fn();
  },
};
const o3 = {
  text: "o3",
  fn: function () {
    var fn = o1.fn;
    return fn();
  },
};
console.log(o1.fn()); // o1
console.log(o2.fn()); // o1
console.log(o3.fn()); // undefined
  • 第二个 console 的 o2.fn(),最终还是调用 o1.fn(),因此答案仍然是 o1

  • 最后一个,在进行 var fn = o1.fn 赋值之后,是“裸奔”调用,因此这里的 this 指向 window,答案当然是 undefined

例7:

如何让o2.fn()输出结果是o2

①使用call/apply/bind来对 this 的指向进行干预

const o1 = {
  text: "o1",
  fn: function () {
    return this.text;
  },
};
const o2 = {
  text: "o2",
  fn: function () {
    return o1.fn.call(o2);
  },
};
console.log(o2.fn());

this 指向最后调用它的对象,在 fn 执行时,挂到 o2 对象上即可

const o1 = {
  text: "o1",
  fn: function () {
    return this.text;
  },
};
const o2 = {
  text: "o2",
  fn: o1.fn,
};
console.log(o2.fn());

例8:

const foo = {
  name: "andy",
  logName: function () {
    console.log(this.name);
  },
};
const bar = {
  name: "mike",
};
foo.logName.call(bar); // mike

例9:

function Foo() {
  this.bar = "Andy";
}
const instance = new Foo();
console.log(instance.bar); // Andy

new 操作符调用构造函数,具体做了什么?

  • 创建一个新的对象;

  • 将构造函数的 this 指向这个新对象;

  • 为这个对象添加属性、方法等;

  • 最终返回新对象。

function Foo() {
  this.bar = "Lucas";
}
let obj = {};
obj.__proto__ = Foo.prototype;
Foo.call(obj);
console.log(obj.bar); //Lucas

例10:

如果在构造函数中出现了显式 return 的情况,那么需要注意分为两种场景:

function Foo() {
  this.user = "foo";
  const o = {};
  return o;
}
const instance = new Foo();
console.log(instance); // {}
console.log(instance.user); // undefined

此时 instance 是返回的空对象 o

function Foo() {
  this.user = "foo";
  return 1;
}
const instance = new Foo();
console.log(instance); // Foo{ user:'foo' }
console.log(instance.user); // foo

如果构造函数中显式返回一个值,且返回的是一个对象,那么 this 就指向这个返回的对象;如果返回的不是一个对象,那么 this 仍然指向实例。

例11:

箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。

const foo = {
  fn: function () {
    setTimeout(function () {
      console.log(this);
    });
  },
};
foo.fn();

this 出现在 setTimeout() 中的匿名函数里,因此 this 指向 window 对象。

const foo = {
  fn: function () {
    setTimeout(() => {
      console.log(this);
    });
  },
};
foo.fn(); // { fn:f }

该函数所在的作用域:箭头函数所在的作用域是fn,因为fn是一个函数
作用域指向的对象:foo.fn指向的对象是foo

所以箭头函数体内的this指向的是foo。

箭头函数注意事项:

  1. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

    var Foo = () => {
      value: 1;
    };
    var foo = new Foo(); // TypeError: Foo is not a constructor
    
  2. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

    var A = function (a) {
      console.log(arguments);
    };
    var B = (b) => {
      console.log(arguments);
    };
    var C = (...c) => {
      console.log(c);
    };
    A(1, 2, 3); // [Arguments]{ '0': 1, '1': 2, '2': 3 }
    B(2, 2, 3); // ReferenceError: arguments is not defined
    C(3, 4); // [ 3, 4 ]
    
  3. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

箭头函数中的this:

1.箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值,自己本身并没有this值;

2.箭头函数的this永远指向其上下文的this,任何方法都改变不了其指向,如call(), bind(), apply()。

例12:

var name = "window";
var A = {
  name: "A",
  sayHello: function () {
    var s = () => console.log(this.name);
    return s;
  },
};
var sayHello = A.sayHello();
sayHello(); // A

var B = {
  name: "B",
};
A.sayHello.call(B)(); //B

sayHello.call(B); // A

sayHello.call(); //A

注意:一旦函数通过bind传递了有效的this对象,则该函数在运行期的this将指向这个对象,即使通过call或apply来试图改变this的指向也是徒劳的。

例13:

callapplybindnewthis 绑定的情况称为显式绑定;根据调用关系确定的 this 指向称为隐式绑定。

new 绑定的优先级比显式 bind 绑定更高。

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

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

var bar = foo.call(obj1)
console.log(bar.call(obj2)) // 123
const a = 123
const foo = () => a => {
    console.log(this.a)
}

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

var bar = foo.call(obj1)
console.log(bar.call(obj2)) //undefined

因为使用 const 声明的变量不会挂载到 window 全局对象当中。因此 this 指向 window 时,自然也找不到 a 变量了。

例14:

原生bind实现

// Funtion.prototype.bind = Funtion.prototype.bind || function(context){}
Function.prototype.myBind = function (context) {
  if (typeof this !== "function") {
    throw new TypeError(`${this} is not a function`);
  }
  //去掉首位
  //outerArgs是传入的参数
  let outerArgs = Array.prototype.slice.call(arguments, 1);
  console.log("----outerArgs----", outerArgs);
  // 这里也可以简写为:
  // let outerArgs = [].slice.call(arguments,1)
  let self = this;
  let F = function () {};
  let Bound = function () {
    let innerArgs = [].slice.call(arguments);
    console.log("----innerArgs----", innerArgs);
    // args是调用myBind返回的新函数时传入的参数
    let args = outerArgs.concat(innerArgs);
    console.log(args);
    // 当新函数是构造函数时,this指向实例对象Bound,否则指向传入的对象
    return self.apply(this instanceof Bound ? this : context, args);
  };
  if (self.prototype) {
    F.prototype = self.prototype;
  }
  // 将Bound的原型指向self的原型
  Bound.prototype = new F();
  return Bound;
};

function func(...arg) {
  console.log(this);
  console.log(...arg);
}
func.prototype.con = function () {
  console.log("con");
};
var newFunc = func.myBind({ a: 1 }, 1, 2, 3, 4);
newFunc();
newFunc.prototype.con(); // con
----outerArgs---- [ 1, 2, 3, 4 ]
----innerArgs---- []
[ 1, 2, 3, 4 ]
{ a: 1 }
1 2 3 4
con

原生call

Function.prototype.myCall = function (context, ...args) {
  //如果context为null / undefined,则指向window
  context = typeof context === "object" ? context : window;
  //防止覆盖掉原有属性
  const key = Symbol();
  //这里的this为需要执行的方法
  context[key] = this;
  //方法执行
  const result = context[key](...args);
  delete context[key];
  return result;
};

原生apply

Function.prototype.myApply = function (context, args) {
  context = typeof context === "object" ? context : window;
  //防止覆盖掉原有属性
  const key = Symbol();
  //这里的this为需要执行的方法
  context[key] = this;
  const result = context[key](...args);
  delete context[key];
  return result;
};
let a = {
  name: "123",
  fn: function (a, b) {
    console.log(this.name);
    console.log(a + b);
  },
};
let b = {
  name: "456",
};
a.fn.myApply(b, [99, 1]);  // 456 100
a.fn.myCall(b,99,1); // 456 100
posted @ 2022-10-23 21:39  小风车吱呀转  阅读(16)  评论(0编辑  收藏  举报