关于this

2018-01-01  本文已影响48人  烈风裘

this是什么

在函数运行时,基于调用位置的条件自动生成的内部对象,可以理解为动态绑定对象到this上。

需要强调的是:

示例:

var foo = "golbal foo";  
var myObj = {foo : 'myObj foo'};  
var say = function(){  
    console.log(this.foo);  
}  
  
myObj.say = say;  
myObj.say(); //结果:myObj foo  
say(); //结果:golbal foo  ,相当于window.say(),内部的this->window对象

this的四个绑定规则及优先级

下面四个为this的绑定优先级规则,第一个优先级最高。判断执行流程需要做的就是找到函数的调用位置并判断使用哪条规则。

1. 函数是否通过new Base()方式绑定?如果是,this绑定新创建的对象

new的调用会自动执行下面代码(示例代码),Base函数中的this指向新创建的对象(一般):

var obj = new Base()

// 1. 创建(或者说构造)一个全新的对象;
var _obj = {}

// 2. 我们将这个空对象的__proto__成员指向了Base函数对象prototype成员对象
_obj.__proto__ = Base.prototype

// 3. 我们将Base函数对象的this指针替换成_obj,然后再调用Base函数
var _return = Base.call(_obj)

// 4 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
if (typeof(_return) === 'object') {
    obj = _return
} else {
    obj = _obj
}

2. 函数是否通过callapplybind显式绑定?如果是,this绑定所指定的对象

强制将foo方法中的this绑定到obj对象上,即使后面再次更新绑定也不生效。

function foo(){  
    console.log(this.a);  
}  
var obj = { a:2 };  
var bar = function(){  
    foo.call(obj);  
};  
bar(); //2  
setTimeout( bar, 1000); //2  
bar.call( window ); //2  

注意:

bind绑定会返回新函数,对新函数无法更改内部的this,原因同上。但是对原函数可以随意切换绑定。

function base () {
  console.log(this.hello)
}
var a = {
  hello:'aaa'
}
var b = {
  hello:'bbb'
}

base.call(a) // aaa
base.call(b) // bbb
var bb = base.bind(b) // 强绑定,返回的bb函数已无法更改this
bb.call(a) // bbb
bb.call(b) // bbb
base.call(a) // aaa
base.call(b) // bbb

3. 函数是否在某个上下文对象中隐式调用?如果是,this绑定到那个上下文对象

function foo(){  
    console.log(this.a);  
}  
var obj1 = {  
    a : 2,  
    foo : foo  
}  
var obj2 = {  
    a : 1,  
    obj1 : obj1  
}  
obj2.obj1.foo();    //结果:2  

foo()执行时的上下文是obj1,因此函数内的this->obj1

注意:

隐式绑定会出现绑定丢失的问题,不过这个很好推理。

var a = "foo";  
function foo(){  
    console.log(this.a);  
}  
function doFoo(fn){     //var fn = obj.foo  
    fn();  
}  
var obj = {  
    a : 2,  
    foo : foo  
}  
doFoo(obj.foo); //"foo"  this->window

var bar = obj.foo
bar(); // "foo" 相当于:window.bar(), this->window
bar.call(obj); // "2" this->obj

setTimeout(obj.foo, 100);   //"foo"  

4. 上述全部不是,则this->window上,如果是严格模式,this->undefined

// 严格模式是?
var a = function ( ) {
    "use strict"
    //...
}

事件中的this

1. 作为DOM事件处理

在事件处理函数中的this指向绑定事件的对象event.currentTarget

document.getElementById('button').addEventListener('click',function (event) {
  console.log(this)
  console.log(event.target === event.currentTarget)
  console.log(event.target === this)
})

currentTarget:绑定事件的元素,恒等于this
target:触发事件的元素,可能是绑定事件元素的子元素接收到了事件

2. 作为内联事件处理

当代码在元素上进行调用处理,this指向的是这个DOM元素,this->$(button)

<button onclick="alert(this.tagName.toLowerCase());"> Show this</button>

function函数中返回,则this指向window,this->window

<button onclick="alert((function(){return this}()));">Show inner this</button>

IIFE中的this

不管IIFE写在哪,里面的this都指向window。相当于是在window下执行IIFE函数。此外,自执行函数返回值由内部函数返回。

注意点

1. 忽略this

nullundefined作为this的绑定对象传入callapplybind,调用时会被忽略,实际应用的是默认绑定规则this->window

function foo(){  
    console.log(this.a);  
}  
var a = 1;  
foo.call(null, 2);          // 1  this->window
foo.apply(undefined, [3]);  //1  this->window
foo.apply(window, [3]);  //1  this->window

2. 间接引用

function foo(){  
    console.log(this.a);  
}  
var a = 2;  
var o = { a : 3,foo : foo};  
var p = { a : 4};  
o.foo();            //3  
(p.foo = o.foo)();  //2 间接引用, 前面返回foo函数,相当于:(foo)(), this->window
var pfoo = o.foo;  
pfoo();         //2 隐式丢失  

3. 箭头函数

箭头函数中的this无法被修改,this指向由外层函数决定,常用于事件处理器或定时器等异步场景

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

等价于:

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

补充

1. bind()实现?

function bind(f, o){  
    if(f.bind){  
        return f.bind(o);  
    }else{  
        return function(){  
            return f.apply(o, arguments);  
        }  
    }  
}  

2. 什么是严格模式?

为了向新版JS语法过度的模式。

放置位置

严格模式编译指示: "use strict"

var a = function ( ) {
    "use strict"
    //...
}

与非严格模式的区别

"use strict"
var a = 123
console.log(a)
console.log(a === window.a)
var obj= { };obj.a=1;obj.b=2;
with(obj){
  alert(a+b)
}

with的作用是将obj对象中的变量在{}中展开可直接访问,注意这没有影响window对象。类似于作用域拼接。

var a= 6;

if(a>2){
    function fn ( ) {
        alert('hi');
    }
    fn( );
} //报错

测试题

1. 为什么要使用this,而不是传参解决问题

在不同的对象环境下执行了它们,达到了复用的效果,而不用为了在不同的对象环境下执行而必须针对不同的对象环境写对应的函数了。

2. 基础call

function identify() {
    return this.name.toUpperCase();
}
function sayHello() {
    var greeting = "Hello, I'm " + identify.call( this );
    console.log( greeting );
}
var person1= {
    name: "Kyle"
};
var person2= {
    name: "Reader"
};
identify.call( person1); // KYLE
identify.call( person2); // READER
sayHello.call( person1); // Hello, I'm KYLE
sayHello.call( person2); // Hello, I'm READER

函数在哪里调用才决定了this到底引用的是啥

3. this不是指向函数本身

function fn(num) {
    console.log( "fn: " + num );
    // count用于记录fn的被调用次数
    this.count++;
}
fn.count = 0;
var i;
for (i=0; i<10; i++) {
    if (i > 5) {
        fn( i );
    }
}
// fn: 6
// fn: 7
// fn: 8
// fn: 9
 
console.log( fn.count ); // 0 -- 耶?咋不是4捏?

4. 继承+引用+this

function Parent() {
            this.a = 1;
            this.b = [1, 2, this.a]; // this.a只在函数体内存在,这里相当于设置“默认值”
            this.c = { demo: 5 };
            this.show = function () {
                console.log(this.a , this.b , this.c.demo );
            }
        }
 function Child() {
     this.a = 2;
     this.change = function () {
         // this中变量就近引用,如果没有就从原型链继续找
         this.b.push(this.a);
         this.a = this.b.length;
         this.c.demo = this.a++;
     }
 }
 Child.prototype = new Parent(); 
 var parent = new Parent();
 var child1 = new Child();
 var child2 = new Child();
 child1.a = 11;
 child2.a = 12;
 parent.show(); // 1 [1,2,1] 5
 child1.show(); // 11 [1,2,1] 5
 child2.show(); // 12 [1,2,1] 5
 child1.change(); // a->5, b->[1,2,1,11], c.demo->4,this就近+继承+引用
 child2.change(); // a->6, b->[1,2,1,11,12], c.demo->5,this就近+继承+引用
 parent.show(); // 1 [1,2,1] 5
 child1.show(); // 5 [1,2,1,11,12] 5
 child2.show(); // 6 [1,2,1,11,12] 5

需要注意的是:

 child2.__proto__ === child1.__proto__ // true
上一篇 下一篇

猜你喜欢

热点阅读