JS:this

一、this到底是什么?

this是一个特别的关键字,被自动定义在所有函数的作用域中。

this是在运行时进行绑定的,并不是在编写时绑定,他的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

this实际上是在函数被调用时发生的绑定,他指向什么完全取决于函数在哪里被调用。

二、两个误解

1.this指向函数自身

2.this指向它的作用域

三、this绑定规则

看看在函数的执行过程中调用位置如何决定this的绑定对象

1.默认绑定(可以把这条规则看作是无法应用其他规则时的默认规则)

function foo(){
    console.log(this.a);
}
let a =2;
foo(); //2

函数调用时应用了this的默认绑定,因此this指向全局对象。在代码中,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定。

注:如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined:

function foo(){
“use strict”
    console.log(this.a);
}
let a =2;
foo(); //TypeError:this is undefined

虽然this的绑定规则完全取决于调用位置,但是只要foo()运行在非strict mode时,默认绑定才能绑定到全局对象;在严格模式下调用foo()则不影响默认绑定:

function foo(){
    “use strict”
    console.log(this.a);
}
let a =2;
(function(){
    foo(); //2
})();

2.隐式绑定

需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。

function foo(){
    console.log(this.a);
}
const obj = {
    a =2,
    foo:foo
};
obj.foo(); //2

调用位置会使用obj上下文来引用函数,因此你可以说函数被调用时obj对象“拥有”或者“包含”函数引用。

当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

注:隐式丢失

一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说他会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。

我们会通过下面的固定this方法来修复这个问题

3.显式绑定

在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上。

那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,可以使用下面方法:

1.call() ;apply()

他们的第一个参数是一个对象,是给this准备的,接着再调用函数时将其绑定到this。因为你可以直接指定this的绑定对象,因此我们称之为显式绑定。

如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(..)、new Boolean(..)、new Number(..)).这通常被称为“装箱”。

2.显式绑定仍然无法解决我们之前提出的丢失绑定问题,但是显式绑定的一个变种可以解决这个问题

(1)硬绑定: 通过apply + 闭包机制 实现bind方法,实现强行绑定规则


function foo(b){
  return this.a+b
}
var obj={
  a:2
}
function bind(fn,obj){
  return function(){
     return fn.apply(obj,arguments)
  }
}
bind(foo,obj)(3)//5

硬绑定是一种非常常用的模式,所以ES5提供了内置的方法Function.porpertype.bind,用法如下:


function foo(b){
  return this.a+b
}
var obj={
  a:2
}
var bar = foo.bind(obj);
bar b = bar(3)

console.log(b);//5

bind(..)会返回一个硬编码的新函数,他会把你指定的参数设置为this的上下文并调用原始函数。

(2)API调用的“上下文”

第三方库的许多函数,以及JS语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和bind(..)一样,确保你的回调函数使用指定的this


function foo (el){
  console.log(el,this.id)
}
var obj ={
 id:'some one'
};
[1,2,4].forEach(foo,obj)
// 1 some one 2 some one 4 some one

4.new绑定

  • 传统面向类的语言中的构造函数,是在使用new操作符实例化类的时候,会调用类中的一些特殊方法(构造函数)
  • 很多人认为js中的new操作符和传统面向类语言的构造函数是一样的,其实有很大的差别
  • 从新认识一下js中的构造函数,js中的构造函数 在被new操作符调用时,这个构造函数不属于每个类,也不会创造一个类,它就是一个函数,只是被new操作符调用。
  • 使用new操作符调用 构造函数时会执行4步
    • 创建一个全新的对象
    • 对全新的对象的__proto__属性地址进行修改成构造函数的原型(prototype)的引用地址
    • 构造函数的this被绑定为这个全新的对象
    • 如果构造函数有返回值并且这个返回值是一个对象,则返回该对象,否则返回当前新对象
function Foo(a){
  this.a=a
}
var F = new Foo(2)
console.log(F.a)//2

注:在new中使用硬绑定函数,主要的目的是预先设置函数的一些参数,这样在使用new进行初始化时就可以只传入其余的参数。bind(...)的功能之一就是可以把除了第一个参数(第一个参数用于绑定this)之外的其他参数都传给下层的函数(这种技术称为“部分应用”,是“柯里化”的一种)

举例来说


function foo(a,b){
   this.val = a+b;
}
//之所以使用null是因为本例中我们不关心硬绑定的this是什么
  反正使用new时this会被改变
var bar = foo.bind(null,"a");

bar baz = new bar("b")

baz.val;//ab

 四、this判断

  • 1 判断该函数是不是被new操作符调用,有的话 this就是 构造函数运行时创建的新对象 var f = new foo()
  • 2 判断 函数是不是使用显式绑定 call、apply、bind,如果有,那么该函数的this就是 这个三个方法的第一个参数 foo.call(window)
  • 3 判断该函数是不是被一个对象的属性引用了地址,该函数有上下文(隐式绑定),在函数执行的时候是通过该对象属性的引用触发,这个函数的this就是当前对象的。 obj.foo();
  • 4 上面的三种都没有的话,就是默认绑定,该函数的this就是全局对象或undefined(严格模式下)

五、绑定例外

1.被忽略的this

把 null、undefined通过 apply、call、bind 显式绑定,这些值在调用时会被忽略,应用的是默认绑定。但是建议这么做因为在非严格的模式下会给全局对象添加属性,有时候会造成不可必要的bug。

那么什么时候我们会传入null?

(1)使用apply(..)来“展开”一个数组,并当作参数传入一个函数

注:在ES6中可以使用...操作符来代替apply(..)来“展开”数组

(2)bind(..)对参数进行柯里化(预先设置一些参数)

但是总是使用null来忽略this绑定可能会产生一些副作用。如果摸个函数确实是用了this,那默认绑定规则会把this绑定到全局对象,这价格、能够导致不可预计的后果。

更安全的this

一种更安全的做法是传入一个特殊的对象,把this绑定到这个对象上。

如果我们在忽略this绑定时总是传入一个DMZ对象,那就不用担心了,因为任何对于this的使用都会被限制在这个空对象中,不会对全局对象产生任何影响

在JS中创建一个空对象最简单的方法是Ob'j'ect.create(null)。

Ob'j'ect.create(null)和{}很像,淡但是并不会创建Ob'j'ect.prototype这个委托,所以他比{}”更空“

function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 我们的空对象
var ø = Object.create( null );
// 把数组展开成参数
foo.apply( ø, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值