javascript中this的深度剖析

来源:互联网 发布:行政审批流程优化方案 编辑:程序博客网 时间:2024/04/29 12:06

一、普通函数this的指向

1、概述

JavaScript的this无论对于高级还是初学开发人员都是一个比较难理解的知识点,可能因为是实际 的开发项目对JavaScript的使用不是那么的深,只是使用JavaScript的常用知识点。其实,真正对JavaScript使用比较深是做框架开发和封装的。但是,有些 知识点或多或少的会使用到,特别是面试的时候,面试官一般都会问些JavaScript的高级知识点,无论实际项目用不用,这样才能显示公司的实力。所以,我们有必要弄明白一些JavaScript的高级知识点。本章节我们首先讲解this的相关知识点。

2、普通函数的this

2、1 我们先看一个实例:

 var a = "1"; function demo() {     var a = "2";     alert(this.a); } new demo(); demo();

上面的实例如果你能把两个结果正确的输出就说明你对this掌握的差不多了。我们来具体解析一下:

this永远指向的是直接调用它的那个对象, 这句话理解起来可能不是非常费劲。我们从另一个方面考虑,如果没有实例化对象, 那么this指向的是谁?

首先 ,我们说一下作用域链:一个执行环境的作用域是把自己的作用域放在最顶端,其次是嵌套它的函数也就是父级,最后是 window全局作用域,这样就形成了一个作用域链。下面我们来说一下this的 指向:如果使用了new 实例化了一个对象,那么this就指向实例化的对象,如果没有出现new 实例化,this用于指向的是window。

实例中 new demo(),实例化了对象,这个时候this 就指向了实例化的对象,但是demo并没有使用this关键字定义a变量,虽然使用var定义了a变量,但是var 定义的变量,并不会放在原型链中,也就是它是私有的,外部无法访问。这个时候通过原型链找不到a属性,所以输出:undefined.

实例中demo()直接调用了函数,并没有new实例化对象,这个时候this指向的是window,通过作用域链这个时候this.a==window.a,这个时候this==window,所以输出外侧的变量:1.

我们把上面的实例修改一下进一步的体验:

var a = "1";function demo() {    var a = "2";    alert(this==window);    alert(this.a);}new demo();demo();

1、new demo()执行第一个alert弹出false,第二个弹出:undefined
2、demo()执行第一个alert弹出true,第二个弹出:1

2、2 另外一个实例

function demo() {    a = "2";    this.a = "11";    alert(a);}demo();alert(a);

上面实例弹出的都是11,这里要说一下变量提升的概念,大家都知道声明变量一般都用var,在函数外部使用var声明的变量,作用域是window, 函数内部声明的变量,如果不使用 var声明的,变量就会提升到 window作用域,使用了var声明的变量作用域就是当前函数的作用域。

这个时候this仍然指向的是window,所以外侧的alert(a) 同样也可以访问a这个变量。

2、3 call修改this指向

var a = 1;function test() {    var a = 2;    console.log(this.a); // 1}test.a = 3;test();

这个实例中this指向的还是window,所有test.a=3不会有任何的作用,最终,输出的还是1。

那么如何修改this的指向拿,这个时候可以使用call和apply方法,来改变this的指向。这里我们只用call说明实例:

var a = 1;function test() {    var a = 2;    console.log(this.a); // 3}var obj = {    a: 3};test.call(obj);

上面实例中,我们把test中的this通过call()方法,指向了obj对象了,所以这个时候this就是 obj了。

二、字面量对象中的this

1、概述

面向字面量对象中的this,也是 一个非常重要的知识点,实际项目开发过程 中使用的面向字面量对象比原型比较多,大部分的图片轮播插件都是使用的它。所以,理解面向字面量对象的this也是刻不容缓的。

2、实例

var age=12;var people={    age:24,    sayAge:function () {        return age;    },    sayAge1:function  () {        return this.age;    }};alert(people.sayAge());//12alert(people.sayAge1());//24

上面实例中分别调用了sayAge()和sayAge1()方法,它们返回的age一个没有this关键字,一个有this关键字。但是,就是因为this关键字它们弹出的结果却不一样,sayAge()弹出:12,sayAge1() 弹出:24.

我们来仔细的分析一下:

首先,上个章节我们说过this永远指向调用它的对象,如果没有实例化对象this永远指向window。面向字面量对象是一个特殊的对象,内部函数已经帮助我们通过 new Object()实例化了字面量对象,所以,我们在使用的时候不需要再次实例化了。

其次,我们知道,在对象的内容调用其他的方法或者属性,一定要使用this关键字,同时,声明的时候如果方法和属性 能够被外侧使用也要使用this关键字。没有用this声明的变量或者方法,外侧是无法访问到的??。

最后,sayAge()方法 直接使用了age,这个时候people在自己的作用域链里面没有查找到age,所以就进行window作用域链的查找,结果找到了。而sayAge1()因为使用了this,people就 查找自己的作用域链,结果就查找到了24.

3、闭包中的this

var age=12;var people={     age:24,     sayAge:function () {         function innerAge() {             return this.age;         }         return innnerAge;     },     sayAge1:function  () {         return this.age;     } };alert(people.sayAge());alert(people.sayAge()());//12,注意是2个括号people.sayAge()()

上面实例 我们在sayAge()方法里面,声明了innerAge()内部函数,并且返回这个函数,这就是所谓的闭包, 调用people.sayAge()()最终弹出的结果是:12。

可能有人会问我这里明明使用了this关键字,为什么返回的最外侧的那个age=12的变量拿?之前我们已经说过了,this永远指向的是调用它的对象,我们虽然通过people.sayAge()(),调用了sayAge()方法,但是因为sayAge() 方法返回的是一个函数,然后我们在执行返回的函数,这就不是对象的调用了,只是普通函数的运行,之前我们说过,普通函数的this永远指向window。所以,innerAge()中的this==window.

如果我们想 使用people的age,可以把上面的代码修改为:

var age=12;var people={    age:24,    sayAge:function () {        var that=this;        function innnerAge() {            return that.age;        }        return innnerAge;    },    sayAge1:function  () {        return this.age;    }};alert(people.sayAge()());//24,注意是2个括号people.sayAge()()

把this赋给that变量,这样that就引用了this,然后在进行使用:that.age, 这样获取的就是people里面的age=24的 属性了。

4、作为对象的方法调用

var age = 12;function sayAge() {    return this.age;}var people = {    age: 24,    sayAge: sayAge,    sayAge1: function() {        return this.age;    }};alert(people.sayAge());//24alert(sayAge());//12

上面实例中,我们把普通函数sayAge()作为了people对象的方法,这个时候sayAge()方法中的this,就会指向people。

5、嵌套函数作用域中的this

var a = 1;function test(){    console.log(this.a); // 2    function test2(){        console.log(this.a); // 1    }    test2();}var obj = {a: 2, fn: test};obj.fn();

上面的例子说明,嵌套函数被调用时并没有继承被嵌套函数的this引用,在嵌套函数被调用时,this指向全局对象。在有些应用中,我们需要在嵌套函数中读取调用被嵌套函数的对象的属性,此时可以声明一个局部变量保存this引用,代码如下所示:

var a = 1;function test(){    console.log(this.a); // 2    var self = this;    function test2(){        console.log(self.a); // 2    }    test2();}var obj = {a: 2, fn: test};obj.fn();

6、多个对象嵌套中的this

例子1:

var o = {    a:10,    b:{        a:12,        fn:function(){            console.log(this.a); //12           }    }}o.b.fn();

情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。

情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象??。

情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,例子1可以证明,如果不相信,那么接下来我们继续看几个例子。

var o = {    a:10,    b:{        // a:12,        fn:function(){            console.log(this.a); //undefined                }    }}o.b.fn();

尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。

还有一种比较特殊的情况,例子2:

var o = {    a:10,    b:{        a:12,        fn:function(){            console.log(this.a); //undefined            console.log(this); //window              }    }}var j = o.b.fn;j();

这里this指向的是window,是不是有些蒙了?其实是因为你没有理解一句话,这句话同样至关重要。

this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子2中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window,这和例子1是不一样的,例子1是直接执行了fn。

构造函数中的this

1、正常实例

var age = 12;function people() {    age=50;//对全局变量的修改    this.age=24;//静态属性}people.sayAge=function(){//静态方法   console.log(age);}people.prototype.sayAge = function () {//实例方法    return this.age;}var p=new people();console.log(people,p);console.log(p.sayAge());//24people.sayAge();//50

这里之所以对象p可以点出函数sayAge里面的age是因为new关键字可以改变this的指向,将这个this指向对象p,为什么我说p是对象,因为用了new关键字就是创建一个对象实例。相当于已经复制了一份sayAge函数到对象p中,用了new关键字就等同于复制了一份。下面是new实例化的过程:

new的过程拆分成以下三步:
(1) var p={}; 也就是说,初始化一个对象p
(2) p.proto = Person.prototype;
(3) Person.call(p); 也就是说构造p,也可以称之为初始化p。

从上面的过程就可以知道为什么this会指向p?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。

2、构造函数return实例

var age = 12;function people() {    age=50;    this.age=24;    return {};    //return {a:'aaa'};}people.sayAge1=function(){   console.log(age);}people.prototype.sayAge = function () {    return this.age;}var p=new people();console.log(p.sayAge());//p.sayAge is not a function

这个实例 我们在构造函数people里面返回了一个空对象,这个时候调用 p.sayAge()会出现:p.sayAge is not a function错误。为什么会这样?我们再来修改一下代码:

var age = 12;function people() {    age=50;    this.age=24;    return 1;    //return undefined;    //return null;    //return ;}people.sayAge1=function(){   alert(age);}people.prototype.sayAge = function () {    return this.age;}var p=new people();alert(p.sayAge());//24

这里我们在构造函数里面return一个数字1、undefined、null、(空),这个时候调用p.sayAge()就能输出24。
下面我们再来修改一下:

var age = 12;function people() {    age=50;    this.age=24;    return {age:444,sayAge:function(){                        return this.age;                    }            };}people.sayAge1=function(){   console.log(age);}people.prototype.sayAge = function () {    return this.age;}var p=new people();console.log(p.sayAge());//444

这里我们返回undefined,p.sayAge()同样可以输出:24。

通过上面的实例,我们可以总结:如果构造函数返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。还有一点就是null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。

3、静态方法中的this

var age = 12;function people() {    this.age=24;}people.sayAge=function(){   console.log(this.age);}people.prototype.sayAge = function () {    return this.age;}var p=new people();people.sayAge();console.log(people.prototype.sayAge());

上面实例第一个 输出:undefined, 第二个也输出:undefined。我们来解释一下:

静态方法不属于实例化对象,是共有的,所以不能有代表某个对象的this。这句话很好理解就是静态方法直接通过函数名就能使用 ,实例化的对象无法访问静态方法。因此, 也就没有所谓的this, 同理也就无法使用this了。

可能还有人会问, 既然sayAge()的this 不是实例化的this, 那么这个时候this应该指向的是window,应该输出:12。好的针对这个问题,我们修改一下方法,打印出this:

people.sayAge=function(){    console.log(this);    alert(this.age);}

可以看到 结果打印出来的是 function people(){},那就说明this指向的是people这个函数,之前我们说过谁调用的this,this就会指向谁。但是现在是我们直接通过函数people直接调用的方法,这个时候根本就不会在people函数中找到this,所以,不会打印出window的age。

下面我们来总结一下:this为何不能用在静态方法中

在类里面的静态方法是不能访问类的非静态成员(实例成员)的,原因很简单,我们要想在本类的方法中访问本类的其它成员,我们需要使用this这个引用,而this这个引用指针是代表调用此方法的对象,我们说了静态的方法是不用对象调用的,而是使用类名来访问,所以根本就没有对象存在,也就没有this这个引用了,没有了this这个引用就不能访问类里面的非静态成员,又因为类里面的静态成员是可以不用对象来访问的,所以类里面的静态方法只能访问类的静态的属性,既然this不存在,在静态方法中访其它静态成员我们使用的是一个特殊的类“self”;self和this相似,只不过self是代表这个静态方法所在的类。所以在静态方法里,可以使用这个方法所在的类的“类名”,也可以使用“self”来访问其它静态成员。

    var age = 12;    function people() {        this.age=24;    }    people.sayAge=function(){       console.log(self.age);    }    people.prototype.sayAge = function () {        return self.age;    }    var p=new people();    people.sayAge();    console.log(people.prototype.sayAge());    console.log(p.sayAge());

终极测试

1、普通函数this

function t(){   this.x=2;}t();console.log(window.x);

输出结果是:2,因为调用t(),这里的this就是window

2、return this的实例

function a(xx) {    this.x = xx;    return this;};var x = a(5);console.log(x);var y = a(6);console.log(x,y);console.log(x.x);console.log(y.x);

这里主要考虑变量提升和变量重命名的相关知识。
分析如下:
开始执行代码时,会创建一个全局对象window
js执行代码过程可以分: 词法分析期和执行期

第一步词法分析包括:形参分析、实参分析、变量声明分析、函数声明分析。分析出的结果作为对象的属性和方法
window对象在词法分析期 得到的属性和方法有:
window.a=function(xx){this.x=xx;return this}
window.x=undefined
window.y=undefined

第二步代码执行期:
x = a(5); 先执行 window.a(5) –> window.a=function(xx) this.x=xx;return this} ,函数中的this指代对象是window.
得出window.x=5, 此时,全局域中window.x=undefined 变成 window.x=5
然后 return window 赋值给 x 即:x=window ,即window.x=window, window.x=5被替换

y = a(6);—>先执行 window.a(6) –>window.a=function(xx){this.x=xx;return this} ,函数中的this指代对象是window.
得出window.x=6, 把全局域中window.x=window变成 window.x=6
然后 return window 赋值给 y 即:y=window ,即把window.y=undefined 变成了 window.y=window

此时:window.x=6 window.y=window
console.log(x.x); //输出x.x 相当于:(window.x).x=6.x —> window对象中没有 6.x属性 则输出undefined
console.log(y.x); //输出 y.x 相当于:(window.y).x=window.x —> window对象中有window.x这个属性 则输出6

3、面向字面量 this

var obj = {    x: 1,    y: 2,    t: function() {        console.log(this.x)    }}obj.t();//1var dog={x:11};dog.t=obj.t;dog.t();//11show=function(){    console.log('show'+this.x);}dog.t=show;dog.t();//11

作为对象的方法来调用,this指向方法的调用者,即母体对象,不管被调用的函数,声明的时候属于方法,还是函数

function cat(name,age){    this.name=name;    this.age=age;    this.bark=function(){        console.log('i am '+this.name);    }}var cat=new cat('huzi',2);cat.bark();//i am huzifunction pig(){    this.age=99;    return 'abc';}var pig=new pig();console.log(pig);//pig对象 

new cat发生以下步骤:
a:系统创建空对象{}。空对象construcor属性指向cat函数
b.把函数的this指向该空对象
c.执行该函数
d.返回该对象

name = 'this is window';var obj1 = {    name: 'php',    t: function() {        console.log(this.name)    }};var dog1 = {    name: 'huzi'};obj1.t();//phpdog1.t = obj1.t;var tmp = dog1.t;tmp(); //this is window(dog1.t = obj1.t)();//this is windowconsole.log(dog1.t = obj1.t,(dog1.t = obj1.t));dog1.t.call(obj1);//huzi

说一下(dog1.t = obj1.t),这表达式,一定有返回值,在这里返回值肯定是t函数的结果。既然强调是值,说明是立即使用函数,其值本身效果等同于(f(){})()
在这里就立即调用this,指向的是null,被解释器解释成 window。
所以说,有this操作的,如this.age=xx的函数最好不要直接调用,要用new调用,因为直接调用的话,this指向window,会污染全局变量。

自执行、闭包

var number=2;var obj={   number:4,   fn1:(function(){ // 匿名函数1           var number;           this.number*=2;// (1)           number=number*2;// (2)           number=3;           return function(){ // 匿名函数(2)                 var num=this.number;                 this.number*=2;                 console.log(num);                 number*=3;                 alert(number);           }    })(),    db2:function(){        this.number*=2;    }}var fn1=obj.fn1; // (3)alert(number);// (4)fn1();// (5)obj.fn1();// (6)alert(window.number);alert(obj.number);

当定义obj的时候执行了匿名函数1(自执行),此时处于全局作用域内,因此上下文this是window。执行完语句(1)导致全局变量number的值变为4(this.number,number为局部变量var number;);执行语句(2)时临时变量number还没有被赋值,所以是NaN,但下一句会将其赋值为3;最后,匿名函数1返回了匿名函数2,因此obj.fn1=匿名函数2。(注意匿名函数2里面会用到临时变量number,老生常谈的闭包)

来到语句(3),这句会把fn1这个变量赋值为obj.fn1,也就是匿名函数2

由于全局变量number已经在语句(1)中变为了4,所以语句(4)弹出的对话框结果为4

语句(5)执行的是fn1(),它与执行obj.fn1()的区别是两者this不一样。前者为null,而后者this为obj。但是又由于JS规定,this为null时相当于全局对象window,所以这句代码执行时函数的this为window。在匿名函数2里会将全局变量number更新为8,同时将匿名函数1中被闭包的临时变量number更新为9

语句(6)的效果在上面已经分析过了,this是obj,所以obj.number更新为8,闭包的number更新为27

原文链接:http://www.68kejian.com/app/detail.html?id=80&&c=468

0 0
原创粉丝点击