资讯专栏INFORMATION COLUMN

【译文】this全解

dreamtecher / 3064人阅读

摘要:避免从构造函数返回任何东西,因为它可能会替换所产生的实例。避免使用使用也能创建一个实例然而这不会调用构造函数。因为不会调用构造函数,所以这是一个有效的创建继承模式的方法,能够重写原型链上的构造函数。

一. 全局 this

1.在浏览器中,在一个全局环境中,this就是window对象。

2.在浏览器中,在全局中使用var相当于分配给this或者window

3.假如你创建一个新的变量,不使用var或者let(ECMAScript6),你是添加或者改变全局this的属性

4.在node中使用repl,this是最顶级的命名空间,你可以认为是global

> this
{ ArrayBuffer: [Function: ArrayBuffer],
  Int8Array: { [Function: Int8Array] BYTES_PER_ELEMENT: 1 },
  Uint8Array: { [Function: Uint8Array] BYTES_PER_ELEMENT: 1 },
  ...
> global === this
true

5.在node中执行脚本,在全局中this是一个空对象,而不与global相等

test.js:
console.log(this);
console.log(this === global);
$ node test.js
{}
false

6.在node中,全局环境中的var并非像在浏览器中执行脚本一样,分配给this

test.js:
var foo = "bar";
console.log(this.foo);
$ node test.js
undefined

但是在repl中是一样的

> var foo = "bar";
> this.foo
bar
> global.foo
bar

7.在node中,使用脚本执行,不用var或者let创建的变量会添加到global而不是this.

test.js
foo = "bar";
console.log(this.foo);
console.log(global.foo);
$ node test.js
undefined
bar

在repl中,它是分配到这两个上的。

二. 函数中的this

除了DOM事件处理程序或者一个thisArg已经设置的情况外,在node和浏览器中,函数中(不实例化new)的this是全局范围的。

test.js:
foo = "bar";

function testThis () {
  this.foo = "foo";
}

console.log(global.foo);
testThis();
console.log(global.foo);
$ node test.js
bar
foo

除非你使用user strictthis会变为underfined

当你new一个函数的时候,this会成为一个新的上下文,不等同于全局this

三. 原型中的this

函数对象有一个特殊的属性prototype,当你创建一个函数实例,可以访问prototype属性,可以使用this进行访问

    function Thing() {
      console.log(this.foo);
    }

    Thing.prototype.foo = "bar";

    var thing = new Thing(); //logs "bar"
    console.log(thing.foo);  //logs "bar"

加入创建多个实例化,它们共享原型上的值,this.foo都会返回相同的值,除非你在实例化函数上进行覆盖。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    console.log(this.foo);
}
Thing.prototype.setFoo = function (newFoo) {
    this.foo = newFoo;
}

var thing1 = new Thing();
var thing2 = new Thing();

thing1.logFoo(); //logs "bar"
thing2.logFoo(); //logs "bar"

thing1.setFoo("foo");
thing1.logFoo(); //logs "foo";
thing2.logFoo(); //logs "bar";

thing2.foo = "foobar";
thing1.logFoo(); //logs "foo";
thing2.logFoo(); //logs "foobar";

this在一个实例中是一个特殊的对象,this实际是一个关键字,可以认为this作为一种方法去访问prototype,直接分配给this,将会覆盖原来prototype上的方法。你可以删除this挂接的方法,从而恢复访问默认prototype

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    console.log(this.foo);
}
Thing.prototype.setFoo = function (newFoo) {
    this.foo = newFoo;
}
Thing.prototype.deleteFoo = function () {
    delete this.foo;
}

var thing = new Thing();
thing.setFoo("foo");
thing.logFoo(); //logs "foo";
thing.deleteFoo();
thing.logFoo(); //logs "bar";
thing.foo = "foobar";
thing.logFoo(); //logs "foobar";
delete thing.foo;
thing.logFoo(); //logs "bar";

或者直接引用函数对象的原型。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    console.log(this.foo, Thing.prototype.foo);
}

var thing = new Thing();
thing.foo = "foo";
thing.logFoo(); //logs "foo bar";

创建的实例都共享相同的属性和方法,如果给prototype分配一个数组,所有实例都能够访问。

function Thing() {
}
Thing.prototype.things = [];


var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");
console.log(thing2.things); //logs ["foo"]

prototype上分配一个数组通常是一个错误,如果希望每个实例都有自己的数组,那在函数中创建。

function Thing() {
    this.things = [];
}


var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");
console.log(thing1.things); //logs ["foo"]
console.log(thing2.things); //logs []

this可以通过原型链找到相应的方法。

function Thing1() {
}
Thing1.prototype.foo = "bar";

function Thing2() {
}
Thing2.prototype = new Thing1();


var thing = new Thing2();
console.log(thing.foo); //logs "bar"

在javascript中可以使用原型链模拟传统面向对象继承。
使用函数内含有绑定this的方法或者属性去创建原型链,将会隐藏上层原型链定义的内容。

function Thing1() {
}
Thing1.prototype.foo = "bar";

function Thing2() {
    this.foo = "foo";
}
Thing2.prototype = new Thing1();

function Thing3() {
}
Thing3.prototype = new Thing2();


var thing = new Thing3();
console.log(thing.foo); //logs "foo"

我喜欢叫绑定在原型上的函数为methods.在methods中使用this绑定某个值,将会覆盖原型上的相关定义。

function Thing1() {
}
Thing1.prototype.foo = "bar";
Thing1.prototype.logFoo = function () {
    console.log(this.foo);
}

function Thing2() {
    this.foo = "foo";
}
Thing2.prototype = new Thing1();


var thing = new Thing2();
thing.logFoo(); //logs "foo";

在JavaScript嵌套函数中,虽然可以捕获到父函数中的变量,但是不继承this

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    var info = "attempting to log this.foo:";
    function doIt() {
        console.log(info, this.foo);
    }
    doIt();
}


var thing = new Thing();
thing.logFoo();  //logs "attempting to log this.foo: undefined"

函数doIt中的this指向global,在use strict下则为undefined,这是很多不熟悉this用法的人痛苦的根源之一。
更坏的情况是,将一个实例方法作为参数传入函数。this将指向global,在use strict下则为undefined

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {  
    console.log(this.foo);   
}

function doIt(method) {
    method();
}

var thing = new Thing();
thing.logFoo(); //logs "bar"
doIt(thing.logFoo); //logs undefined

一些人把this赋值给一个变量,通常叫self,能够避免this指向global这个问题。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    var self = this;
    var info = "attempting to log this.foo:";
    function doIt() {
        console.log(info, self.foo);
    }
    doIt();
}

var thing = new Thing();
thing.logFoo();  //logs "attempting to log this.foo: bar"

但是这种方法在将一个实例方法作为参数传入函数情况下,不起作用

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () { 
    var self = this;
    function doIt() {
        console.log(self.foo);
    }
    doIt();
}

function doItIndirectly(method) {
    method();
}


var thing = new Thing();
thing.logFoo(); //logs "bar"
doItIndirectly(thing.logFoo); //logs undefined

解决这个方法,可以使用函数绑定的方法bind

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () { 
    console.log(this.foo);
}

function doIt(method) {
    method();
}


var thing = new Thing();
doIt(thing.logFoo.bind(thing)); //logs bar

你也可以使用apply或者call在新的上下文环境中调用方法或者函数。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () { 
    function doIt() {
        console.log(this.foo);
    }
    doIt.apply(this);
}

function doItIndirectly(method) {
    method();
}

var thing = new Thing();
doItIndirectly(thing.logFoo.bind(thing)); //logs bar

可以使用bind替换this,适用于任何函数或方法,即使没有在实例原型上定义。

function Thing() {
}
Thing.prototype.foo = "bar";


function logFoo(aStr) {
    console.log(aStr, this.foo);
}


var thing = new Thing();
logFoo.bind(thing)("using bind"); //logs "using bind bar"
logFoo.apply(thing, ["using apply"]); //logs "using apply bar"
logFoo.call(thing, "using call"); //logs "using call bar"
logFoo("using nothing"); //logs "using nothing undefined"

避免从构造函数返回任何东西,因为它可能会替换所产生的实例。

function Thing() {
    return {};
}
Thing.prototype.foo = "bar";


Thing.prototype.logFoo = function () {
    console.log(this.foo);
}


var thing = new Thing();
thing.logFoo(); //Uncaught TypeError: undefined is not a function

奇怪的是,假如你返回的是原始值(string或者number),返回语句将会被忽略。最好不要从你打算调用的构造函数中返回任何东西,即使你知道你在做什么。如果你想创建一个工厂模式,使用一个函数来创建实例,不要用new的。当然,这只是个人观点。
避免使用new`,使用Object.create也能创建一个实例

function Thing() {
}
Thing.prototype.foo = "bar";


Thing.prototype.logFoo = function () {
    console.log(this.foo);
}


var thing =  Object.create(Thing.prototype);
thing.logFoo(); //logs "bar"

然而这不会调用构造函数。

function Thing() {
    this.foo = "foo";
}
Thing.prototype.foo = "bar";


Thing.prototype.logFoo = function () {
    console.log(this.foo);
}


var thing =  Object.create(Thing.prototype);
thing.logFoo(); //logs "bar"

因为Object.create不会调用构造函数,所以这是一个有效的创建继承模式的方法,能够重写原型链上的构造函数。

function Thing1() {
    this.foo = "foo";
}
Thing1.prototype.foo = "bar";

function Thing2() {
    this.logFoo(); //logs "bar"
    Thing1.apply(this);
    this.logFoo(); //logs "foo"
}
Thing2.prototype = Object.create(Thing1.prototype);
Thing2.prototype.logFoo = function () {
    console.log(this.foo);
}

var thing = new Thing2();
四. 对象中的this

可以在对象的任何函数中使用this来引用该对象上的其他属性。这与使用new实例不同。

var obj = {
    foo: "bar",
    logFoo: function () {
        console.log(this.foo);
    }
};

obj.logFoo(); //logs "bar"

不使用new,Object.create ,function 去创建一个对象,也可以像实例化一样绑定到对象上。

var obj = {
    foo: "bar"
};

function logFoo() {
    console.log(this.foo);
}

logFoo.apply(obj); //logs "bar"

当你像下面使用this时,没有顺着对象的层次结构。只有直接父对象上的属性才能通过this进行访问

var obj = {
    foo: "bar",
    deeper: {
        logFoo: function () {
            console.log(this.foo);
        }
    }
};

obj.deeper.logFoo(); //logs undefined

你可以直接使用你想要的属性。

var obj = {
    foo: "bar",
    deeper: {
        logFoo: function () {
            console.log(obj.foo);
        }
    }
};

obj.deeper.logFoo(); //logs "bar"
五. DOM event中的this

在一个HTML DOM event处理程序中,this通常是指DOM element event绑定的对象

function Listener() {
    document.getElementById("foo").addEventListener("click",
       this.handleClick);
}
Listener.prototype.handleClick = function (event) {
    console.log(this); //logs "
" } var listener = new Listener(); document.getElementById("foo").click();

除非你绑定新的上下文

function Listener() {
    document.getElementById("foo").addEventListener("click", 
        this.handleClick.bind(this));
}
Listener.prototype.handleClick = function (event) {
    console.log(this); //logs Listener {handleClick: function}
}

var listener = new Listener();
document.getElementById("foo").click();
六. HTML中的this

在HTML属性中可以放js代码,this指向当前的元素

this的覆盖
你不能够复写this,因为它是一个关键词

function test () {
    var this = {};  // Uncaught SyntaxError: Unexpected token this 
}
七. eavl中的this

可以使用eavl访问this

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    eval("console.log(this.foo)"); //logs "bar"
}

var thing = new Thing();
thing.logFoo();

这种做法有安全隐患,可以使用Function访问this

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = new Function("console.log(this.foo);");

var thing = new Thing();
thing.logFoo(); //logs "bar"
八. with中的this

可以使用withthis添加到当前的范围来读取和写入值,而不用显式调用。

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    with (this) {
        console.log(foo);
        foo = "foo";
    }
}

var thing = new Thing();
thing.logFoo(); // logs "bar"
console.log(thing.foo); // logs "foo"

很多人认为这是错的做法,鉴于with引起的歧义。

九. jQuery中的this

像HTML DOM elements的事件处理程序一样,jQuery在很多地方使用this指向DOM元素。比如$.each

鉴于笔者翻译水平有限,有什么问题欢迎提出指教。

十.参考资料

原文地址:all this

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/80248.html

相关文章

  • 《JS编程全解》—— 回调函数

    摘要:事件驱动正是一种回调函数设计模式。由于不支持多线程,所以为了实现并行处理,不得不使用回调函数,这逐渐成为了一种惯例。上面的回调函数只是单纯的函数而不具有状态。如果回调函数具有状态,就能得到更为广泛的应用。 回调函数模式 回调函数与控制反转 回调函数是程序设计的一种方法。这种方法是指,在传递了可能会进行调用的函数或对象之后,在需要时再分别对其进行调用。由于调用方与被调用方的依赖关系与通常...

    mj 评论0 收藏0
  • 全解小程序猜数字游戏 04《 程序员变现指南之 微信&QQ 小程序 真的零基础开发宝

    摘要:此时使用设置当前值中的猜测值为输入框的内容值。接着判断猜测之是否大于或者小于,因为这两者是范围之外不再进行判断,所以最开始使用进行判断不能小于不能大于以上代码中表示调用微信小程序接口弹出提示,传入的参数为提示内容。 ...

    不知名网友 评论0 收藏0
  • lisahost:美国新的全解锁原生IP 206段上架开售,支持tiktok等,季付122元起

    摘要:怎么样好不好最近美国新的全解锁原生段上架开售,支持等,原一期产品可加价更换年付免费更换,美国原生段仍有少量剩余,套餐原价基础上每月加价元可更换美国原生,更换的为纯净新分配美国段,绝对传家宝产品,支持解锁美区游戏,,等,同时支持。lisahost怎么样?lisahost好不好?lisahost最近美国新的全解锁原生IP 206段上架开售,支持tiktok等,原CN2一期产品可加价更换(年付免费...

    xingqiba 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<