JavaScript基础--this解析

liuxk

this关键字是javascript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。
this既不指向函数本身也不指向函数的语法作用域。
this是在函数被调用时发生的绑定,this的绑定和函数声明的位置没有任何关系,它指向什么完全取决于函数在哪里被调用。

调用位置:是函数在代码中被调用的位置,而不是声明的位置。

this的4条绑定规则

* 默认绑定
* 隐式绑定
* 显式绑定
* new绑定

优先级排序:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
默认绑定

无法应用其它规则时的默认规则
如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined。
最常用的函数调用类型:独立函数调用。

function foo(){
    console.log(this.a)
}
var a = 2;
foo() //2  >函数调用时应用了this的默认绑定,因此this指向全局对象

'tips: 声明在全局作用域中的变量就是全局对象的同名属性。'

//严格模式 example 1
function (){
    "use strict";
    console.log(this.a)
}
var a = 2;
foo(); //TypeError:this is undefined

//严格模式 example 2
function foo(){
    console.log(this.a)
}
var a = 2;
(function(){
    "use strict";
    foo(); //2
})();
隐式绑定

隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
当函数引用上下文对象时,对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。

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

//example 2:
var obj2 = {
    a: 42,
    foo: foo
}
var obj1 = {
    a: 2,
    obj2: obj2
}
obj1.obj2.foo();//42
显式绑定

直接指定this的绑定对象称为显式绑定
常见的显式绑定方法有 call()apply()

call和apply的使用方法:

function.call(thisArg, arg1, arg2, ...)

//thisArg           在 f unction 函数运行时使用的 this 值

//arg1, arg2, ...   指定的参数列表。


func.apply(thisArg, [argsArray]) 

//thisArg    在 f unc 函数运行时使用的this值,
             //如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。

//argsArray  一个数组或者类数组对象,其中的数组元素将作为单独的参数传给'func'函数,
            //如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。
            
            
二者区别
call()方法接受的是一个参数列表,而apply()方法接受的是一个包含多个参数的数组。

function foo(){
    console.log(this.a)
}
var obj={
    a: 2
}
foo.call(obj); //2 调用foo时强制把它的this绑定到obj上

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

1>硬绑定
//硬绑定--显式的强制绑定
function foo(){
    console.log(this.a)
}
var obj = {
    a:2
}
var bar = function(){
    foo.call(obj)
}
bar(); //2

//硬绑定--硬绑定的使用场景就是创建一个包裹函数,负责接收参数并返回值
function foo(something){
    console.log(this.a,something);
    return this.a + something;
}
var obj={
    a: 2
}
var bar=function(){
    return foo.apply(obj,arguments)
}
var b = bar(3); //2 3
console.log(b); //5

//硬绑定--另一种使用方法是创建一个可以重复使用的辅助函数
function foo(something){
    console.log(this.a,something)
    return this.a + something
}
//简单的辅助绑定函数
function bind(fn,obj){
    return function(){
        return fn.apply(obj,arguments)
    }
}
var obj = {
    a:2
}
var bar = bind(foo,obj);
var b = bar(3) //2 3
console.log(b) //5

//由于硬绑定是一种非常常用的模式,所以ES5提供了内置的方法Function.prototype.bind
function foo(something){
    console.log(this.a,something)
    return this.a+something
}
var obj={
    a:2
}
var bar = foo.bind(obj); //bind(...)会返回一个硬编码的新函数,它会把你指定的参数设置为this的上下文并调用原始函数
var b=bar(3); //2 3
console.log(b); //5

bind(...)的功能之一就是可以把除了第一个参数(第一个参数用于绑定this)之外的其他参数都传给下一层的函数


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

new绑定

在Javascript中,构造函数只是一些使用new操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。它们只是被new操作符调用的普通函数。
实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。

function foo(a){
    this.a = a
}
var bar = new foo(2)
console.log(bar); //2

使用new调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
1、创建(或者说构造)一个全新的对象
2、这个新对象会被执行[[Prototype]]连接
3、这个新对象会绑定到函数调用的this
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

绑定例外

1、如果你把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
一种安全的做法是传入一个特殊对象,把this绑定到这个对象不会对你的程序产生任何副作用。
在Javascript中创建一个空对象最简单的方法都是Object。create(null)。Object。create(null)和{}很像,但是并不会创建Object.prototype这个委托,所以它比{}更空。

2、有可能创建一个函数的“间接引用”,在这中情况下,调用这个函数会应用默认绑定规则。

function foo(){
    console.log(this.a)
}
var a =2
var o = {
    a: 3,
    foo: foo 
}
var p = {
    a: 4
}
0.foo(); //3
(p.foo = o.foo)(); //2   >>p.foo = o.foo的返回是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()

3、软绑定 Function.prototype.softBind

if(!Function.prototype.softBind){
    Function.prototype.softBind = function(obj){
        var fn = this;
        //捕获所有的 curried 参数
        var curried = [].slice.call(arguments,1);
        var bound = function(){
            return fn.apply(
                (!this || this === (window || global)) ? 
                    obj : this,
                curried.concat.apply(curried,arguments)
            )
        }
        bound.prototype = Object.create(fn.prototype)
        return bound
    }
}

箭头函数

之前介绍的4条规则可以包含所有正常的函数,但是在es6中介绍了一种无法使用这些规则的特殊函数:箭头函数。

  • 箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
  • 箭头函数的绑定无法被修改。(new也不行)
  • 箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)。这其实和ES6之前代码中的self=this机制一样。

小知识点

柯里化

即Currying的音译。Currying是编译原理层面实现多参函数的一个技术。
Currying 为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数,在某些编程语言中(如 Haskell),是通过 Currying 技术支持多参函数这一语言特性的。

判断this绑定

先找到这个函数的直接调用位置,然后顺序应用下面这4条规则判断this的绑定对象:

  • 由new调用?绑定到新创建的对象。
  • 由call或apply或bind调用?绑定到指定的对象。
  • 由上下文对象调用?绑定到那个上下文对象。
  • 默认:在严格模式下绑定到undefined,否则绑定到全局对象。

文章摘取来源:《你不知道的JavaScript上卷》
 
 

更多文章
JavaScript基础--作用域与变量提升

 
 

阅读 656

sort out
常用技术整理
91 声望
0 粉丝
0 条评论
你知道吗?

91 声望
0 粉丝
宣传栏