图说js中的this——深入理解javascript中this指针

没搞错吧!js写了那么多年,this还是会搞错!没搞错,javascript就是回搞错!

…………

在写java的时候,this用错了,idea都会直接报错!

比如……

但是,js,……idea,爱莫能助了……

在面向对象编程里有两个重要的概念:一个是类,一个是实例化的对象,类是一个抽象的概念,用个形象的比喻表述的话,类就像一个模具,而实例化对象就是通过这个模具制造出来的产品,实例化对象才是我们需要的实实在在的东西,类和实例化对象有着很密切的关系,但是在使用上类的功能是绝对不能取代实例化对象,就像模具和模具制造的产品的关系,二者的用途是不相同的。

有上面代码我们可以看到,this指针在java语言里只能在实例化对象里使用,this指针等于这个被实例化好的对象,而this后面加上点操作符,点操作符后面的东西就是this所拥有的东西,例如:姓名,工作,手,脚等等。

其实javascript里的this指针逻辑上的概念也是实例化对象,这一点和java语言里的this指针是一致的,但是javascript里的this指针却比java里的this难以理解的多,究其根本原因我个人觉得有三个原因:

原因一:javascript是一个函数编程语言,怪就怪在它也有this指针,说明这个函数编程语言也是面向对象的语言,说的具体点,javascript里的函数是一个高阶函数,编程语言里的高阶函数是可以作为对象传递的,同时javascript里的函数还有可以作为构造函数,这个构造函数可以创建实例化对象,结果导致方法执行时候this指针的指向会不断发生变化,很难控制。

原因二:javascript里的全局作用域对this指针有很大的影响,由上面java的例子我们看到,this指针只有在使用new操作符后才会生效,但是javascript里的this在没有进行new操作也会生效,这时候this往往会指向全局对象window。

原因三:javascript里call和apply操作符可以随意改变this指向,这看起来很灵活,但是这种不合常理的做法破坏了我们理解this指针的本意,同时也让写代码时候很难理解this的真正指向

上面的三个原因都违反了传统this指针使用的方法,它们都拥有有别于传统this原理的理解思路,而在实际开发里三个原因又往往会交织在一起,so,this,云里雾里了……

入门书:professionnal Javascript for web devolopers,——高级的说法是这样的:

this总是指向调用该方法的对象!

var name="zhoulujun";

function say(){

console.log(this.name)

}

say(); //zhoulujun

在script标签里我们可以直接使用this指针,this指针(指向window对象,结果)就是window对象,即使使用三等号它们也是相等的。全局作用域常常会干扰我们很好的理解javascript语言的特性,这种干扰的本质就是:

在javascript语言里全局作用域可以理解为window对象,记住window是对象而不是类,也就是说window是被实例化的对象,这个实例化的过程是在页面加载时候由javascript引擎完成的,整个页面里的要素都被浓缩到这个window对象,因为程序员无法通过编程语言来控制和操作这个实例化过程,所以开发时候我们就没有构建这个this指针的感觉,常常会忽视它,这就是干扰我们在代码里理解this指针指向window的情形。

这里this指向window对象,所以this.name->zhoulujun!

当执行 say函数的时候, JavaScript 会创建一个 Execute context (执行上下文),执行上下文中就包含了 say函数运行期所需要的所有信息。 Execute context 也有自己的 Scope chain, 当函数运行时, JavaScript 引擎会首先从用 say函数的作用域链来初始化执行上下文的作用域链。

这方面的知识,建议参考:

http://blog.csdn.net/wangxiaohu__/article/details/7260668

http://www.jb51.net/article/30706.htm

这里可以大致记一下:

var myObj={

name:"zhoulujun",

fn:function(){

console.log(this.name)

}

};

myObj.fn();

这里的this指向obj,因为fn()运行在obj里面……

然后再来看……

var name="zhoulujun";

function say(){

console.log(this.name)

console.log(this)

}

say();

function say2(){

var site="zhoulujun.cn";

console.log(this.site);

}

say2();

myObj2={

site:"zhoulujun.cn",

fn:function(){

console.log(this.site)

}

}

这里的this指向的是对象myObj2,因为你调用这个fn是通过myObj2.fn()执行的,那自然指向就是对象myObj2,这里再次强调一点,this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,一定要搞清楚这个

然后,我们更深入(受不了 …………

myObj3={

site:"zhoulujun.cn",

andy:{

site:"www.zhoulujun.cn",

fn:function(){

console.log(this.site)

}

}

};

myObj3.andy.fn();

这里同样也是对象Object点出来的,但是同样this并没有执行它,那你肯定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不准确,接下来我将补充一句话,我相信你就可以彻底的理解this的指向的问题。

如果,你实在理解不了,就这么样背下来吧!

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

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

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

这样既对了吗??深入点(就受不了了……讨厌……

myObj3={

site:"zhoulujun.cn",

andy:{

site:"www.zhoulujun.cn",

fn:function(){

console.log(this)

console.log(this.site)

}

}

};

//    myObj3.andy.fn();

var fn=myObj3.andy.fn;

fn();

其实,这里的 fn等价于

fn:function(age){

console.log(this.name+age);

}

下面我们来聊聊函数的定义方式:声明函数和函数表达式

我们最上面第一个案例定义函数的方式在javascript语言称作声明函数,第二种定义函数的方式叫做函数表达式,这两种方式我们通常认为是等价的,但是它们其实是有区别的,而这个区别常常会让我们混淆this指针的使用,我们再看看下面的代码:

为什么say可以执行,say3不可以?那是因为:

为什么say3打印结果是undefined,我在前文里讲到了undefined是在内存的栈区已经有了变量的名称,但是没有栈区的变量值,同时堆区是没有具体的对象,这是javascript引擎在预加载扫描变量定义所致,但是ftn01的打印结果很令人意外,既然打印出完成的函数定义了,而且代码并没有按顺序执行,这只能说明一个问题:

在javascript语言通过声明函数方式定义函数,javascript引擎在预处理过程里就把函数定义和赋值操作都完成了,在这里我补充下javascript里预处理的特性,其实预处理是和执行环境相关,在上篇文章里我讲到执行环境有两大类:全局执行环境和局部执行环境,执行环境是通过上下文变量体现的,其实这个过程都是在函数执行前完成,预处理就是构造执行环境的另一个说法,总而言之预处理和构造执行环境的主要目的就是明确变量定义,分清变量的边界,但是在全局作用域构造或者说全局变量预处理时候对于声明函数有些不同,声明函数会将变量定义和赋值操作同时完成,因此我们看到上面代码的运行结果。由于声明函数都会在全局作用域构造时候完成,因此声明函数都是window对象的属性,这就说明为什么我们不管在哪里声明函数,声明函数最终都是属于window对象的原因了。

这里推荐看下——java一个类的执行顺序:

http://www.zhoulujun.cn/zhoulujun/html/java/javaBase/7704.html

其实在javascript语言里任何匿名函数都是属于window对象,它们也都是在全局作用域构造时候完成定义和赋值,但是匿名函数是没有名字的函数变量,但是在定义匿名函数时候它会返回自己的内存地址,如果此时有个变量接收了这个内存地址,那么匿名函数就能在程序里被使用了,因为匿名函数也是在全局执行环境构造时候定义和赋值,所以匿名函数的this指向也是window对象,所以上面代码执行时候fn的this都是指向window,因为javascript变量名称不管在那个作用域有效,堆区的存储的函数都是在全局执行环境时候就被固定下来了,变量的名字只是一个指代而已。

类似的情况(面试题喜欢这么考!)……比如:

this都是指向实例化对象,前面讲到那么多情况this都指向window,就是因为这些时候只做了一次实例化操作,而这个实例化都是在实例化window对象,所以this都是指向window。我们要把this从window变成别的对象,就得要让function被实例化,那如何让javascript的function实例化呢?答案就是使用new操作符。

再来看 构造函数:

function  User(){

this.name="zhoulujun";

console.log(this);

}

var andy=new User();

console.log(andy.name)

why andy 的name  是 zhoulujun,那是:因为:

new关键字可以改变this的指向,将这个this指向对象andy,

那andy什么时候又成了思密达,oh,no,is Object?

因为用了new关键字就是创建一个对象实例(重要的事情默读三遍)

我们这里用变量andy创建了一个User用户实例(相当于复制了一份User到对象andy里面),此时仅仅只是创建,并没有执行,而调用这个函数User的是对象andy,那么this指向的自然是对象andy,那么为什么对象User中会有name,因为你已经复制了一份User函数到对象andy中,用了new关键字就等同于复制了一份。

java 程序猿: Class user=new User();似曾相识木有……

function既是函数又可以表示对象,function是函数时候还能当做构造函数,javascript的构造函数我常认为是把类和构造函数合二为一,当然在javascript语言规范里是没有类的概念,但是我这种理解可以作为构造函数和普通函数的一个区别,这样理解起来会更加容易些

下面我贴出在《javascript高级编程》里对new操作符的解释:

new操作符会让构造函数产生如下变化:

1.       创建一个新对象;

2.       将构造函数的作用域赋给新对象(因此this就指向了这个新对象);

3.       执行构造函数中的代码(为这个新对象添加属性);

4.       返回新对象

……

妈的:读的那么拗口,不明觉厉…………看图……还不

不明白……

var myObj5={

name:"andy"

};

var myObj6=new Object();

myObj6.name="andy";

function  say5(name){

console.log(name)

}

var say6=new Function("name","console.log(name)");

console.log(myObj5)

console.log(myObj6)

say5("andy");

say6("andy");

还不明白,就请奶奶买块豆腐,撞死算了……

第四点也要着重讲下,记住构造函数被new操作,要让new正常作用最好不能在构造函数里写return,没有return的构造函数都是按上面四点执行,有了return情况就复杂了

return这王八蛋……

那么我这样呢……

does it have to be like this?Tell me why(why),is there something I have missed?

Tell me why(why),cos I don't understand…………

那是因为……because of u?no  return……

所以:如果返回的是基本类型,就会丢掉…只能返回Object类型……typeof  xx ===“object”

看到called 没有?什么鬼!!

其实new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。

var a={

name:"andy",

site:"zhoulujun.cn",

fn:function(age){

console.log(this.name+age);

}

};

var b={

name:"zhoulujun",

site:"www.zhoulujun.cn",

fn:function(age){

console.log(this.name+age);

}

};

a.fn(2); //andy2

a.fn.call(b,2) //zhoulujun2

a.fn.apply(b,[2])//zhoulujun2

当然,还有bind……

var arr = [1, 2];

var add = Array.prototype.push.bind(arr, 3);

// effectively the same as arr.push(3)

add();

// effectively the same as arr.push(3, 4)

add(4);

console.log(arr);

// <- [1, 2, 3, 3, 4]

在下面的例子,this将无法在作用域链中保持不变。这是规则的缺陷,并且常常会给业余开发者带来困惑。

function scoping () {

console.log(this);

return function () {

console.log(this);

};

}

scoping()();

// <- Window

// <- Window

有一个常见的方法,创建一个局部变量保持对this的引用,并且在子作用域中不能有同命变量。子作用域中的同名变量将覆盖父作用域中对this的引用。

function retaining () {

var self = this;

return function () {

console.log(self);

};

}

retaining()();

// <- Window

除非你真的想同时使用父作用域的this,以及当前this值,由于某些莫名其妙的原因,我更喜欢是使用的方法.bind函数。这可以用来将父作用域的this指定给子作用域。

function bound () {

return function () {

console.log(this);

}.bind(this);

}

bound()();

// <- Window

写到这里,都看不下去,逻辑有点混乱,有的是从前辈哪里引用的。

改天有时间整理下,然后,在去讲下闭包(……closer

参考文章:

http://blog.jobbole.com/81018/

http://blog.jobbole.com/74110/

http://blog.jobbole.com/54267/

http://www.cnblogs.com/aaronjs/archive/2011/09/02/2164009.html#commentform

http://www.codeceo.com/article/javascript-this-pointer.html

转载请注明文章来源:图说js中的this--深入理解javascript中this指针 - js - 周陆军的个人网站

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,565评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,021评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,003评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,015评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,020评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,856评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,178评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,824评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,264评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,788评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,913评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,535评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,130评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,102评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,334评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,298评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,622评论 2 343

推荐阅读更多精彩内容

  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,718评论 2 17
  • 首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上...
    Mr__王阅读 728评论 0 3
  • 首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁...
    马里奥Joseph阅读 341评论 0 3
  • 在面向对象的语言中(例如Java,C#等),this含义是明确且具体的,即指向当前对象。一般在编译期绑定。而在ja...
    一木_qintb阅读 393评论 0 0
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,054评论 1 10