this指向

在JavaScript中,this的指向是调用时决定的,而不是创建时决定的。

一、全局上下文中

在全局上下文中,thiswindow 对象,用 var 声明的变量以及用 function 声明的函数都会挂载在 window 上,letconst 声明的变量则不会。

console.log(this);// Window{}
console.log(this === window);// true
var a = 10;
let b = 20;
const c = 30;
console.log(this.a === window.a);// true
console.log(this.b);// undefined
console.log(this.c);// undefined

二、函数上下文中

在函数内部,this 的值取决于函数被调用的方式。

1、普通函数的this指向

在没有开启严格模式的情况下直接在全局调用函数,函数内 this 指向 window,开启严格模式为则 undefined

// 不开启严格模式
function foo(){
    console.log(this);// Window{}
    return this;
}
console.log(foo() === window); // true

/** ----------------- 分割线 ------------------- */

// 开启严格模式
"use strict";
function foo(){
    console.log(this);// undefined
    return this;
}
console.log(foo() === window); // false

2、箭头函数的this指向

箭头函数会捕获其所在上下文的this值,作为自己的this值。

function foo() { 
    console.log(this);// obj
    setTimeout(()=>{
        console.log(this.a);// 2
        console.log(this === obj);// true
    },100)
}
var obj = {
    a: 2
}
foo.call(obj);

3、对象内部函数的this指向

var person = {
    age: 18,
    getAge: function() {
        console.log('getAge...', this);
    },
    getName: a => {
        console.log('getName...', this, a);
    }
}
person.getAge();// person
person.getName();// window

var getAge = person.getAge;
var getName = person.getName;
getAge();// window
getName();// window
getAge.call(person);// person
getName.call(person, 1);// window 箭头函数不能通过bind,call,apply改变this指向,但是参数可以传过去

4、实例对象的this指向

1、构造函数上面的箭头函数this指向始终指向实例对象(箭头函数会捕获其所在上下文的this值,作为自己的this值),普通函数指向调用者
2、构造函数原型对象上的方法,普通函数this指向调用者,箭头函数this指向window

// 构造函数上面的箭头函数this指向始终指向实例对象,普通函数指向调用者
function Person () {
    this.getName = (name = 'lsyana') => {
        console.log(name, this);// this始终指向实例对象
    };
    this.getFriend = function(friend = 'lucy') {
        console.log(friend, this);
    };
}
// 构造函数原型对象上的方法,普通函数this指向调用者,箭头函数this指向window
Person.prototype.getAge = function (age = 18) {
    console.log(age, this);
}
Person.prototype.sayHi = (msg = 'hahahah~') => {
    console.log(msg, this);
}

var person = new Person();
person.getName();// 'lsyana', Person {getName: ƒ, getFriend: ƒ}
person.getFriend();// 'lucy', Person {getName: ƒ, getFriend: ƒ}
person.getAge();// 18, Person {getName: ƒ, getFriend: ƒ}
person.sayHi();// 'hahahah~', Window{}

console.log('---------分割线---------');

var {getName, getFriend, getAge, sayHi} = person;
getName();// 'lsyana', Person {getName: ƒ, getFriend: ƒ}
getFriend();// 'lucy', Window{}
getAge();// 18, Window{}
sayHi();// 'hahahah~', Window{}

console.log('---------分割线---------');

var p1 = {parent: 'p1', getName, getFriend, getAge, sayHi};
p1.getName();// 'lsyana', Person {getName: ƒ, getFriend: ƒ}
p1. getFriend();// 'lucy', {parent: 'p1', getName: ƒ, getFriend: ƒ, getAge: ƒ, sayHi: ƒ}
p1.getAge();// 18, {parent: 'p1', getName: ƒ, getFriend: ƒ, getAge: ƒ, sayHi: ƒ}
p1.sayHi();// 'hahahah~', Window{}

new过程发生了什么?

通过new操作符调用构造函数的5个步骤:

  1. 创建一个新对象
  2. 将新对象内部的 [[Prototype]] 特性被赋值为构造函数的 prototype 属性
  3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
  4. 执行构造函数内部的代码(给新对象添加属性)
  5. 如果构造函数返回对象,则返回该对象;否则,返回刚创建的新对象(空对象)
function _new(Fn, ...args) {
    // Object.create(现有的对象) 使用现有的对象作为新对象的原型
    // 1、新建一个对象,并将构造函数的原型作为新对象的原型,继承构造函数原型上的方法
    var obj = Object.create(Fn.prototype);
    // 2、将构造函数的this指向新的对象,执行构造函数内部代码并拿到返回值
    var res = Fn.apply(obj, args);
    // 3、构造函数有返回则返回构造函数的返回值,没有则返回新对象obj
    return res instanceof Object ? res : obj;
}

/** -------------- 也可以写成下面这样子 -------------- */

function _new(Fn, ...args) {
    // 1、新建一个对象
    var obj = {};
    // 2、新对象的原型指针指向构造函数的原型,继承构造函数原型上的方法
    obj.__proto__ = Fn.prototype;
    // 3、将构造函数的this指向新的对象,执行构造函数内部代码并拿到返回值
    var res = Fn.apply(obj, args);
    // 4、构造函数有返回则返回构造函数的返回值,没有则返回新对象obj
    return res instanceof Object ? res : obj;
}

5、改变函数的this指向

Function.prototype.call(thisArg, arg1, arg2, …)

在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
call实现:

  1. 将函数设为对象的属性
  2. 使用 obj.fn() 的方式执行该函数并且拿到返回值(调用 fn 的是obj,此时 this 指向 obj
  3. 删除对象上该函数
var obj = {
    age: 18
}
function getAge(a, b) {
    console.log(this, this.age, a, b);
}
Function.prototype._call = function (context, ...args) {
    // this为调用_call的函数getAge
    console.log('原来的this > this:', this);
    // context为传入的对象
    console.log('传入的对象context > context:', context);

    // 1. 当传入的this指向对象为null/undefined时,this指向window
    context = context || window;
    // 2. 给传入的对象上添加一个函数,并且赋值为调用的函数getAge
    context.fn = this;
    // 3. 调用getAge,此时this为调用它的对象context(也就是传入的obj)
    context.fn(...args);
    // 4. 上面的操作会给传入的对象添加了一个多余的属性fn,用完删掉它,就跟原来的一样了
    delete context.fn;
};
getAge._call(obj, 1, 2);// {age: 18, fn: ƒ} 18 1 2
getAge._call(null, 1, 2);// Window {window: Window, self: Window, document: document, name: '', location: Location, …}  undefined 1 2
Function.prototype.apply(thisArg[, argsArray])

传入一个指定的 this 对象值和一个有多个参数组成的数组,将 this 指向对象并执行函数。

apply实现:

Function.prototype._apply = function (context, args) {
    context = context || window;
    context.fn = this;
    var res = context.fn(...args);
    delete context.fn;
    return res;
};
Function.prototype.bind(thisArg[, arg1[, arg2[, …]]])

创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

bind实现:

Function.prototype._bind = function (context, ...args) {
    context = context || window;
    context.fn = this;
    return function () {
        return context.fn(...args);
    }
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值