Home > Web Front-end > JS Tutorial > Front-end advanced five: comprehensive interpretation of this

Front-end advanced five: comprehensive interpretation of this

PHPz
Release: 2017-04-04 17:42:28
Original
1153 people have browsed it

Front-end advanced five: comprehensive interpretation of this

~

In the process of learning JavaScript, we don’t understand some concepts very clearly, but we want to By writing it down in some way, it is easy to hastily draw some biased conclusions about these concepts to facilitate your own memory.

What is more harmful is that some inaccurate conclusions are widely circulated on the Internet.

For example, in the understanding of what this points to, there is a saying: whoever calls it, this points to. When I first started learning this, I believed in this sentence very much. Because in some cases, this understanding makes sense. But I often encounter some different situations in development. An incorrect call due to this can make me confused for a whole day. At that time, I also looked up information and asked experts in the group, but I still couldn't figure out "Where did I go wrong?" In fact, it's just because I have an inaccurate conclusion in my mind.

Here is a complaint about BaiduSearch. Many of the articles and knowledge points found in the search are wrong, which has harmed labor and management for a long time.

So, I I think there is a need for such an article to help everyone understand this in an all-round way. Let everyone have a correct and comprehensive understanding of this.

Before this, we need to review the execution context.

In the previous articles, I mentioned the lifecycle of the execution context in several places. In case you don’t remember it, let’s review it again, as shown below.

Front-end advanced five: comprehensive interpretation of this

Execution context life cycle

In the creation phase of the execution context, variables## will be generated separately #Object, establish the scope chain and determine the point of this. We have carefully summarized the variable object and scope chain, and the key here is to determine the point of this.

Here, we need to draw a very important conclusion that must be kept in mind. The point of

this is determined when the function is called. It is determined when the execution context is created. Therefore, we can easily understand that the this pointer in a function can be very flexible. For example, in the following example, the same function points to different objects due to different calling methods.

var a = 10;
var obj = {
    a: 20
}

function fn () {
    console.log(this.a);
}

fn(); // 10
fn.call(obj); // 20
Copy after login
In addition,

During the execution of the function, once this is determined, it cannot be changed.

var a = 10;
var obj = {
    a: 20
}

function fn () {
    this = obj; // 这句话试图修改this,运行后会报错
    console.log(this.a);
}

fn();
Copy after login

1. This in the global object

Regarding this in the global object, I mentioned it before when summarizing the variable object. It is a A rather special existence. This in the global environment points to itself. So it's also relatively simple and there aren't that many complications to consider.

// 通过this绑定到全局对象
this.a2 = 20;

// 通过声明绑定到变量对象,但在全局环境中,变量对象就是它自身
var a1 = 10;

// 仅仅只有赋值操作,标识符会隐式绑定到全局对象
a3 = 30;

// 输出结果会全部符合预期
console.log(a1);
console.log(a2);
console.log(a3);
Copy after login
2. This in the function
Before summarizing the point of this in the function, I think we need to go through some strange examples to feel the elusiveness of this in the function.

// demo01
var a = 20;
function fn() {
    console.log(this.a);
}
fn();
Copy after login
// demo02
var a = 20;
function fn() {
    function foo() {
        console.log(this.a);
    }
    foo();
}
fn();
Copy after login
// demo03
var a = 20;
var obj = {
    a: 10,
    c: this.a + 20,
    fn: function () {
        return this.a;
    }
}

console.log(obj.c);
console.log(obj.fn());
Copy after login
These examples require readers to take some time to experience them. If you don’t understand what’s going on, don’t worry, we will analyze them bit by bit.

Before the analysis, we first directly draw the conclusion.

In a function context, this is provided by the caller and is determined by the way the function is called.

If the caller function is owned by an object, then when the function is called, the internal this points to the object. If the function is called independently, then this inside the function points to undefined. But in non-strict mode, when this points to undefined, it will automatically point to the global object.

We can see from the conclusion that if you want to accurately determine the point of this, it is very important to find the caller of the function and distinguish whether he is an independent call.

// 为了能够准确判断,我们在函数内部使用严格模式,因为非严格模式会自动指向全局
function fn() {
    'use strict';
    console.log(this);
}

fn();  // fn是调用者,独立调用
window.fn();  // fn是调用者,被window所拥有
Copy after login
In the above simple example,

fn() as an independent caller, according to the definition, its internal this pointer is undefined. And window.fn()because fn is owned by window, the internal this points to the window object.

Now that you have mastered this rule, now go back and look at the three examples above. By adding/removing strict mode, you will find that this has become less illusory and has traces. Followed.

但是我们需要特别注意的是demo03。在demo03中,对象obj中的c属性使用this.a + 20来计算,而他的调用者obj.c并非是一个函数。因此他不适用于上面的规则,我们要对这种方式单独下一个结论。

当obj在全局声明时,无论obj.c在什么地方调用,这里的this都指向全局对象,而当obj在函数环境中声明时,这个this指向undefined,在非严格模式下,会自动转向全局对象。可运行下面的例子查看区别。

'use strict';
var a = 20;
function foo () {
    var a = 1;
    var obj = {
        a: 10, 
        c: this.a + 20,
        fn: function () {
            return this.a;
        }
    }
    return obj.c;

}
console.log(foo()); // 运行会报错
Copy after login
  • 实际开发中,并不推荐这样使用this;

  • 上面多次提到的严格模式,需要大家认真对待,因为在实际开发中,现在基本已经全部采用严格模式了,而最新的ES6,也是默认支持严格模式。

再来看一些容易理解错误的例子,加深一下对调用者与是否独立运行的理解。

var a = 20;
var foo = {
    a: 10,
    getA: function () {
        return this.a;
    }
}
console.log(foo.getA()); // 10

var test = foo.getA;
console.log(test());  // 20
Copy after login

foo.getA()中,getA是调用者,他不是独立调用,被对象foo所拥有,因此它的this指向了foo。而test()作为调用者,尽管他与foo.getA的引用相同,但是它是独立调用的,因此this指向undefined,在非严格模式,自动转向全局window。

稍微修改一下代码,大家自行理解。

var a = 20;
function getA() {
    return this.a;
}
var foo = {
    a: 10,
    getA: getA
}
console.log(foo.getA());  // 10
Copy after login

灵机一动,再来一个。如下例子。

function foo() {
    console.log(this.a)
}

function active(fn) {
    fn(); // 真实调用者,为独立调用
}

var a = 20;
var obj = {
    a: 10,
    getA: foo
}

active(obj.getA);
Copy after login
三、使用call,apply显示指定this

JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有着两个方法。它们除了参数略有不同,其功能完全一样。它们的第一个参数都为this将要指向的对象。

如下例子所示。fn并非属于对象obj的方法,但是通过call,我们将fn内部的this绑定为obj,因此就可以使用this.a访问obj的a属性了。这就是call/apply的用法。

function fn() {
    console.log(this.a);
}
var obj = {
    a: 20
}

fn.call(obj);
Copy after login

而call与applay后面的参数,都是向将要执行的函数传递参数。其中call以一个一个的形式传递,apply以数组的形式传递。这是他们唯一的不同。

function fn(num1, num2) {
    console.log(this.a + num1 + num2);
}
var obj = {
    a: 20
}

fn.call(obj, 100, 10); // 130
fn.apply(obj, [20, 10]); // 50
Copy after login

因为call/apply的存在,这让JavaScript变得十分灵活。因此就让call/apply拥有了很多有用处的场景。简单总结几点,也欢迎大家补充。

  • 将类数组对象转换为数组

function exam(a, b, c, d, e) {

    // 先看看函数的自带属性 arguments 什么是样子的
    console.log(arguments);

    // 使用call/apply将arguments转换为数组, 返回结果为数组,arguments自身不会改变
    var arg = [].slice.call(arguments);

    console.log(arg);
}

exam(2, 8, 9, 10, 3);

// result: 
// { '0': 2, '1': 8, '2': 9, '3': 10, '4': 3 }
// [ 2, 8, 9, 10, 3 ]
// 
// 也常常使用该方法将DOM中的nodelist转换为数组
// [].slice.call( document.getElementsByTagName('li') );
Copy after login
  • 根据自己的需要灵活修改this指向

var foo = {
    name: 'joker',
    showName: function() {
      console.log(this.name);
    }
}
var bar = {
    name: 'rose'
}
foo.showName.call(bar);
Copy after login
// 定义父级的构造函数
var Person = function(name, age) {
    this.name = name;
    this.age  = age;
    this.gender = ['man', 'woman'];
}

// 定义子类的构造函数
var Student = function(name, age, high) {

    // use call
    Person.call(this, name, age);
    this.high = high;
}
Student.prototype.message = function() {
    console.log('name:'+this.name+', age:'+this.age+', high:'+this.high+', gender:'+this.gender[0]+';');
}

new Student('xiaom', 12, '150cm').message();

// result
// ----------
// name:xiaom, age:12, high:150cm, gender:man;
Copy after login

简单给有面向对象基础的朋友解释一下。在Student的构造函数中,借助call方法,将父级的构造函数执行了一次,相当于将Person中的代码,在Sudent中复制了一份,其中的this指向为从Student中new出来的实例对象。call方法保证了this的指向正确,因此就相当于实现了基层。Student的构造函数等同于下。

var Student = function(name, age, high) {
    this.name = name;
    this.age  = age;
    this.gender = ['man', 'woman'];
    // Person.call(this, name, age); 这一句话,相当于上面三句话,因此实现了继承
    this.high = high;
}
Copy after login
  • 在向其他执行上下文的传递中,确保this的指向保持不变

如下面的例子中,我们期待的是getA被obj调用时,this指向obj,但是由于匿名函数的存在导致了this指向的丢失,在这个匿名函数中this指向了全局,因此我们需要想一些办法找回正确的this指向。

var obj = {
    a: 20,
    getA: function() {
        setTimeout(function() {
            console.log(this.a)
        }, 1000)
    }
}

obj.getA();
Copy after login

常规的解决办法很简单,就是使用一个变量,将this的引用保存起来。我们常常会用到这方法,但是我们也要借助上面讲到过的知识,来判断this是否在传递中被修改了,如果没有被修改,就没有必要这样使用了。

var obj = {
    a: 20,
    getA: function() {
        var self = this;
        setTimeout(function() {
            console.log(self.a)
        }, 1000)
    }
}
Copy after login

另外就是借助闭包与apply方法,封装一个bind方法。

function bind(fn, obj) {
    return function() {
        return fn.apply(obj, arguments);
    }
}

var obj = {
    a: 20,
    getA: function() {
        setTimeout(bind(function() {
            console.log(this.a)
        }, this), 1000)
    }
}

obj.getA();
Copy after login

当然,也可以使用ES5中已经自带的bind方法。它与我上面封装的bind方法是一样的效果。

var obj = {
    a: 20,
    getA: function() {
        setTimeout(function() {
            console.log(this.a)
        }.bind(this), 1000)
    }
}
Copy after login
四、构造函数与原型方法上的this

在封装对象的时候,我们几乎都会用到this,但是,只有少数人搞明白了在这个过程中的this指向,就算我们理解了原型,也不一定理解了this。所以这一部分,我认为将会为这篇文章最重要最核心的部分。理解了这里,将会对你学习JS面向对象产生巨大的帮助。

结合下面的例子,我在例子抛出几个问题大家思考一下。

function Person(name, age) {

    // 这里的this指向了谁?
    this.name = name;
    this.age = age;   
}

Person.prototype.getName = function() {

    // 这里的this又指向了谁?
    return this.name;
}

// 上面的2个this,是同一个吗,他们是否指向了原型对象?

var p1 = new Person('Nick', 20);
p1.getName();
Copy after login

我们已经知道,this,是在函数调用过程中确定,因此,搞明白new的过程中到底发生了什么就变得十分重要。

Calling the constructor through the newoperator will go through the following 4 stages.

  • Create a new object;

  • Point the this of the constructor to this new object;

  • Point to the code of the constructor, add properties, methods, etc. to this object;

  • Return the new object.

Therefore, when the new operator calls the constructor, this actually points to the newly created object, and finally the new object is returned and received by the instance object p1. Therefore, we can say that at this time, this of the constructor points to the new instance object, p1.

The this on the prototype method is much easier to understand. According to the definition of this in the function above, the getName in p1.getName() is the caller, and he is owned by p1 , so this in getName also points to p1.

Okay, everything I know about this has been summarized. I hope everyone can really learn something after reading it, and then give me a like ^_^. If you find any errors, please point them out in the comments and I will fix them as soon as possible. Thanks in advance.

The above is the detailed content of Front-end advanced five: comprehensive interpretation of this. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template