JavaScript中的this

来源:互联网 发布:免费的sql数据库 编辑:程序博客网 时间:2024/06/16 10:44

运行环境

JavaScript代码主要有两种运行环境,一种是常用的浏览器环境,还有一种就是node环境,本文中如无特殊说明,均指浏览器环境。

this的含义

首先,this关键字总是返回一个对象。
其次,this的指向是动态的,即this的指向是可以发生变化的,this对象是在运行时基于函数的执行环境绑定的,即只有在运行的时候才能知道this的值(bind函数除外)。

this的使用场景

this的使用场景主要有以下这五个使用场景。
- 全局对象
- 构造函数
- 对象的方法
- DOM事件
- call/apply/bind

下面,我将详细阐述这五个使用场景。
其中,call/apply/bind单独作为一个单元来讲。

1. 全局对象中的this

在全局环境中使用this,无论是直接运行还是在函数内部运行,它指向的就是顶层对象window。

this === window; // true(function(){console.log(this === window)})(); // truefunction f() {console.log(this === window)}; f(); // true

通过上例,我们可以看到在浏览器上,以上三段代码均返回true。但在node中并不完全一致。

// 以下代码请在node环境中执行console.log(this === global); // false(function(){console.log(this === global)})(); // truefunction f() {console.log(this === global)}; f(); // true

在node环境下,只有在函数中,this 才会指向顶层对象 global。

2. 构造函数中的this

1.构造函数中的 this 指向实例对象。

function Person(name) {    this.name = name;}Person.prototype.sayName = function() {    return this.name;}var p = new Person('kylin');console.log(p.name); // kylinp.sayName(); // kylin

2.原型继承中的this

var Parent = function() {    this.get = function() {        return this;    }}var Son = function() {}Son.prototype = new Parent();var son = new Son();son.get(); // son{}

这个例子也说明了,this 对象和调用方法的对象有关,即使在继承中也不例外。

3. 对象方法中的this

1.直接使用对象调用对象中的方法,其方法中的 this 指向该对象。

var person = {    name: 'kylin',    getName: function() {        return this.name;    }}person.getName(); // kylin

2.需要注意的是,当A对象的方法被赋值给了B对象时,方法中的 this 指向就从A对象变成了B对象。

var personA = {    name: 'kylin',    getName: function() {        return this.name;    }}var personB = {    name: 'lover'}personB.getName = personA.getName;personB.getName(); // lover

3.如果某个方法位于多层对象的内层,这时 this 也只会指向当前这一层的对象,而不会继承更上面的层的对象。

var outerObj = {    a: 'hello',    b: {        // a: 'world',        c: function() {            return this.a;        }    }};outerObj.b.c(); // undefined

如果将注释掉的内容恢复,就会返回 world。

4.如果将对象的方法赋值给一个变量,那么 this 指向会变成 window 对象。

var name = 'the window';var obj = {    name: 'kylin',    getName: function() {        return this.name;    }}var f = obj.getName;f(); // the window

5.方法内部定义的函数,一般而言其执行环境具有全局性,即 this 指向顶层对象window。

var name = 'the window';var obj = {    name: 'my ojb',    getNameFunc: function() {        return function() {            console.log(this.name); // the window        }    }};obj.getNameFunc()();var obj2 = {    name: 'my ojb',    getNameFunc: function() {        function f() {            console.log(this.name); // the window            console.log(this === window); // true        }        f();    }};obj2.getNameFunc();

实际开发中,我们确实有在方法中定义新的函数的需求,一般而言,我们会使用闭包将 this 传入内部函数。

var name = 'the window';var obj = {    name: 'my ojb',    getNameFunc: function() {        return function f() {            console.log(this.name); // the window            console.log(this === window); // true        }    }};obj.getNameFunc()();var obj2 = {    name: 'my ojb',    getNameFunc: function() {        var _this = this;        return function f() {            console.log(_this.name); // my obj            console.log(_this === window); // false        }    }};obj2.getNameFunc()();

6.比较特殊的,在方法中处理数组

var o = {  v: 'hello',  p: [ 'a1', 'a2' ],  f: function f() {    this.p.forEach(function (item) {      console.log(this.v + ' ' + item);    });  }};o.f();// undefined a1// undefined a2

与上面的示例一样,数组处理方法中的 this 也是指向顶层对象window。

7.对象属性中的this

var obj = {    name: 'outer',    self: this,    getName: function() {        console.log(this.name); // outer        console.log(this.self === window); // true    },    inner: {        name: 'inner',        self: this,        getName: function() {            console.log(this.name); // inner            console.log(this.self === window); // true        },    }}obj.getName();obj.inner.getName();

具体JavaScript为什么这么设计,我也不太明白,有知道的同学麻烦告诉我。

8.函数数组中的this

function f1() {return this};function f2() {return this};var arr = [f1, f2];arr[0](); // arr[]

其实这个并不难理解,arr数组其实就是arr对象,[]运算符可以理解为对象中的 . 运算符,只不过在对象中凡是能用 . 运算符的时候都能用 [] 运算符,但是在某些特殊情况下只能用 [] 运算符。调用可以这样理解

// 仅仅作为辅助理解作用,语法上并不合适arr.0()

很明显,会输出 arr 对象嘛。

9.下面来看几个比较奇怪的例子

var name = 'the window';var obj = {    name: 'kylin',    getName: function() {        console.log(this.name);    }};obj.getName(); // kylin(obj.getName)(); // kylin(obj.getName = obj.getName)(); // the window(true && obj.getName)(); // the window(false || obj.getName)(); // the window(1, obj.getName)(); // the window

相信实际开发中不会有人这么写,仅作为开拓视野的部分,不再详细展开。
其基本原理是一致的,最后四个代码片段,第一个小括号最后都会返回一个函数并且在全局作用域下执行。

4. DOM事件中的this

DOM事件处理程序中的 this ,一般都是指向绑定事件的Element节点。
但里面有两个特殊的事件处理程序的 this 对象指向 window 对象。

// HTML级事件处理程序<div id="root" onclick="showMSG()"/>function showMSG() {    console.log(this === window); // true}// IE事件处理程序<div id="root"/>var rootEle = document.getElementById('root');rootEle.attachEvent('onclick', function () {    console.log(this === window); // true});

有对DOM事件还不太了解的同学可以看我的这篇文章DOM事件;

绑定this

1. function.prototype.call()

1.call 方法接受多个参数,其中第一个参数为要绑定的对象,其他的参数为函数调用需要的参数。

var name = 'the window';var obj = {    name: 'kylin',};var f = function() {    console.log(this.name);}f(); // the windowf.call(obj); // kylin

2.call 方法中的参数,如果参数为空、null、undefined,则默认传入 window 对象。

var name = 'the window';var obj = {    name: 'kylin',    getName: function() {        console.log(this.name);    }};var obj2 = {    name: 'obj'};obj.getName.call(obj2); // objobj.getName.call(); // the windowobj.getName.call(null); // the windowobj.getName.call(undefined); // the window

3.如果 call 方法中的参数是原始值,那么将传入其包装类型。

var name = 'the window';var obj = {    name: 'kylin',    getName: function() {        console.log(this);    }};var obj2 = {    name: 'obj'};obj.getName.call(obj2); // obj{}obj.getName.call(1); // Number{}obj.getName.call('string'); // String{}obj.getName.call(false); // Boolean{}

4.call 方法的一个经典实用场景是调用对象的原生方法。

var str = 'my str';console.log(str.toString()); // my strObject.prototype.toString.call(str); // [object String]

2. function.prototype.apply()

apply方法和call方法类似,唯一的不同是其接受数组作为函数执行时的参数。

var name = 'the window';var obj = {    name: 'kylin',};var f = function() {    console.log(this.name);}f(); // the windowf.apply(obj); // kylin

3. function.prototype.bind()

1.bind 函数用于将函数体内的 this 绑定到某个对象上,然后返回一个新的函数。

var obj = {    count: 0,    inc: function() {        console.log(++this.count);    }};var f1 = obj.inc;f1(); // NaNvar f2 = obj.inc.bind(obj);f2(); // 1

2.bind 函数在绑定 this 的同时,还可以绑定原函数的参数。

var add = function(x, y) {    return x * this.m + y * this.n;}var obj = {    m: 2,    n: 3}var newAdd = add.bind(obj, 5);newAdd(10); // 40

3.如果 bind 函数的第一个参数为null、undefined,则默认绑定顶层对象。

var m = 5,     n = 10;var add = function(x, y) {    console.log(x * this.m + y * this.n);}var newAdd1 = add.bind();newAdd1(5, 10); // 125var newAdd2 = add.bind(null, 5);newAdd2(10); // 125var newAdd3 = add.bind(undefined, 5);newAdd3(10); // 125

4.请注意,bind函数每次都返回一个新函数。

var obj = {name: 'kylin'};var f = function() {return this}var f1 = f.bind(obj);var f2 = f.bind(obj);f1 === f2; // falsef1() === f2(); // true

5. 箭头函数

一般而言,this 指向和执行时的对象有关,而和定义时的对象无关,但是对于箭头函数而言并不是这样。

var name = 'the window';var obj = {    name: 'kylin',    getNameFunc: function(){        return () => {            return this.name;        }    }};obj.getNameFunc()(); // kylinvar f = obj.getNameFunc();f(); // kylinvar obj2 = {    name: 'kylin',    getNameFunc: function(){        return function() {            return this.name;        }    }};obj2.getNameFunc()(); // the windowvar f2 = obj2.getNameFunc();f2(); // the window

致谢

本文主要参考了阮一峰老师的文章this 关键字和《JavaScript高级程序设计》,谨在此向阮一峰老师和JavaScript的作者译者表示感谢。

联系我

如果您有任何疑问或本文侵犯了您的著作权,请联系我。 mail to kylin

原创粉丝点击