狗日的this

来源:互联网 发布:sql语言教程 编辑:程序博客网 时间:2024/06/10 20:46

本文主要总结自《JavaScript 语言精粹》、部分总结自《JavaScript 高级程序设计》以及自己的经验

四种调用模式

在 JavaScript 中,this 的值取决于调用模式,有四种调用模式,分别是方法调用模式、函数调用模式、构造器调用模式、Apply、call 调用模式。

方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法。当方法被调用时(通过 . 表达式或 [subscript] 下标表达式),this 绑定到该对象。

var name = "window",    lzh = {        name: "lzh",        sayName: function(){            alert(this.name); // 输出 "lzh"        }    }lzh.sayName();

函数调用模式

当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的,以此模式调用函数时,this 被绑定到全局对象。
这是语言设计上的一个错误。倘若语言设计正确,那么当内部函数被调用时,this 应该仍然绑定到外部函数的 this 变量。
这个设计错误的后果是方法不能利用内部函数来帮助它工作。
ECMAScript6 的箭头函数(注意只是箭头函数)基本纠正了这个设计上的错误(注意只是基本上,但不是彻底地纠正了错误)

var name = "window",    lzh = {        name: "lzh",        sayName: function(){            innerFunction();            function innerFunction(){                alert(this.name);            }            return function(){                alert(this.name);            }        }    }lzh.sayName()();

上面这段代码 alert 的均是 window,从上面可以看出,不管外部环境的 this 是不是 window,通过函数调用模式调用的函数,this 指向 window。

来看一段 ES6 箭头函数中的 this (上面提到箭头函数基本纠正了设计上的错误)

var name = 'window';var lzh = {    name: 'lzh',    sayName: function(){        return ()=> {            console.log(this.name);        }    }}var iny = {    name: 'iny'}lzh.sayName().apply(iny); // 输出 lzh

其实转换成 ES5 是这么干的:

var name = 'window';var lzh = {    name: 'lzh',    sayName: function(){        var _this = this;        return function(){            console.log(_this.name);        }    }}var iny = {    name: 'iny'}lzh.sayName().apply(iny); // lzh

但如果ES6 中这么写

var name = "window";var lzh = {    name: 'lzh',    sayName: () => {        console.log(this.name)    }}var iny = {    name: 'iny'}lzh.sayName(); // windowlzh.sayName.apply(iny); // window

转换成 ES5 却是这样的

var name = "window";var _this = this;var lzh = {    name: 'lzh',    sayName: function() {        console.log(_this.name)    }};var iny = {    name: 'iny'}lzh.sayName(); // windowlzh.sayName.apply(iny); // window// 有点失望

构造器调用模式

JavaScript 是一门基于原型继承的语言。这意味着对象可以直接从其他对象继承属性。该语言是无类型的。
当今(书的中文版第一版出版时间是2009年)大多数语言都是基于类的语言。尽管原型继承极富表现力,但它未被广泛理解。
JavaScript 本身对它原型的本质也缺乏信心,所以它提供了一套和基于类的语言类似的对象构建语法。
如果在一个函数前面带上 new 来调用,那么背地里将会创建一个连接到该函数的 prototype 成员的新对象,同时 this 会绑定到那个新对象上
如果构造函数返回的不是对象,则通过 new 调用构造函数返回背地里创建的对象。

var Person = function(name){    this.name = name;}Person.prototype.getName = function(){    return this.name;}var lzh = new Person("lzh");console.log(lzh.getName()); // lzh

Apply、call 调用模式

因为 JavaScript 是一门函数式的面向对象编程语言,所以函数可以拥有方法。
每个函数都包含两个非继承而来的方法:apply()和 call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是arguments 对象。call()方法与 apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call()方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来

function sum(num1, num2){    console.log(this);    return num1 + num2;}function callSum1(num1, num2){    return sum.apply(this, arguments); // 传入 arguments 对象}function callSum2(num1, num2){    return sum.apply(null, [num1, num2]); // 传入数组}function callSum3(num1, num2){    return sum.call(null, num1, num2); // 一个一个地传递参数}alert(callSum1(10,10)); //20alert(callSum2(10,10)); //20alert(callSum3(10,10)); //20

从上面的代码可以看出,apply 的第一个参数是一个改变 sum 函数的 this 的值,但这里不论传进去的是 window 还是 null,内部 console.log 出来的都是 window 对象,还可以看出,apply 的第二个参数要么是 arguments、要么是一个数组。call 从第二个参数开始,就要一个一个的传递参数,而不能传递数组或arguments。

单从上面的代码,不能很好的看出 apply、call 的长处,既然 call 能设置 this,那么就能复用其它对象的方法,比如下面这个:

var lzh = {    name: 'lzh',    say: function(something){        alert(something + this.name);    }}var iny = {    name: 'iny'}lzh.say.apply(iny, ['hi, I am ']); // 输出 hi I am iny
  • iny 对象没有 say 方法,但是又希望复用 lzh 的 say 方法,那么就可以用 apply 或 call
  • 这样还是不能很明显的看出 call、apply 的优越性,举个现实点的例子,如何把 arguments 转换成数组,因为 arguments 不是数组,是一个 Array like 的对象,就是有下标元素,可以通过 arguments[0]、arguments[1] 来访问它的元素,但它没有数组的各种方法,比如 popshiftslice 等,操作 arguments 会不大方便,所以我们希望把 arguments 转换成 数组。如果我们大概明白 Array.prototype.slice 的实现原理的话,我们可以利用这个方法将 arguments 转换成数组。
  • 第一步,讲一下 Array.prototype.slice 简易版的大概实现原理(原版应该是使用 C++ 实现的,功能和性能上都比这个简易版的要好):
Array.prototype.slice = function(start, end){    var newArray = [];    if(start >= 0 && end <= this.length){        for(var i = start; i < end; i++){            newArray.push(this[i]);        }    }    return newArray;}var testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];console.log(testArray.slice(0, 2)); // [1, 2]
  • 从上面我们可以看出,假如我们用 Array.prototype.slice.apply(arguments, 0, arguments.length),就相当于把 slice 内部的 this 换成了 arguments; 就可以把 arguments[0]、arguments[1]...等 push 到一个新数组,这样就可以成功的把 arguments 转换成数组了,于是就可以利用数组的各种方法操作参数。当然,这里只是简易地重写了一遍 slice,真实的 slice 可以不传递这里的第三个参数,默认从 0 截取到末尾。
  • apply、call 在实现函数柯里化、对象继承上也有很大的作用,这里不详细展开。

匿名函数中的 this

  • 在网上看了很多关于 this 的博客,都有介绍到匿名函数中的 this 指向 window 对象,但这种说法是不正确的,关键还是要看怎么调用(就是前面介绍的4中调用方式),比如下面的代码
var name = "window",    lzh = function(){        return function(){            //这里是匿名函数,但是 this 的值只有在调用的时候才能确定            alert(this.name);        }    }var iny = {    name: 'iny',    sayName: lzh()}lzh()(); // windowiny.sayName(); // iny

从上面可以看出,this 的值在调用的时候决定

  • 还有就是事件处理程序里面的 this
    在 DOM0 级、DOM2 级的事件处理程序中(onclick/addEventListener),this 指向绑定事件的那个元素,虽然不知道浏览器内部的具体实现,但可以猜测它是由 DOM 对象以方法调用的,属于 DOM 对象的方法,而在 IE 旧版本的实现中,attachEvent 指定的事件处理程序的调用模式应该是函数调用模式,所以 this 指向 window。如有错误,还请指出。
  • setTimeout、setInterval 里面的 this 也指向 window,这个应该还是由调用模式决定的。

下面看几道题目

  • 题目1
var name = "window",    lzh = {        name: "lzh",        sayName: function(){            alert(this.name);            alert(name);        }    }lzh.sayName();

题目1先 alert lzh,再 alert window,alert window 的原因是:sayName 实际上是一个闭包,它的活动对象里有 this(指向 lzh 对象),但没有 name,所以它往父级作用域链寻找 name, 于是找到了全局作用域的变量对象中的 windw,所以 alert window,如果想了解闭包的更多内容,可以点这里。

  • 题目2
var name = "The Window";var object = {    name : "My Object",    getNameFunc : function(){        return function(){            return this.name;        };    }};alert(object.getNameFunc()());

滑动查看答案:alert 的是 "The Window"

  • 题目3
var name = "window",    person = {        name: 'lzh',        getName: function(){            return this.name;        }    }console.log(person.getName());console.log((person.getName)());console.log((person.getName = person.getName)());

滑动查看答案:输出顺序:lzh、lzh、window

  • 如果本文对您有帮助,不妨点赞一下,您的鼓励是我的动力,我会更努力地写出好文章。





javascript的this,一个不知道究竟属于谁的东西

this是一个大利器,用好了就可以帮我们省掉很多事,然而事实上却总是让我们出错。自己就吃过很大的亏。现在咱们就来扒一扒this究竟是什么。
自己看过很多博客或者帖子,看了之后总是感觉当时明白了。后面用的时候总是各种混乱。其实并非人家讲得不好,实在是自己没有真正理解到,后面通过做项目才算是看透了一些东西。

首先我们得死记一句话,this指向的是当前对象。

那么这个对象究竟指的是什么。什么是对象。我们从以下几个方面来理解:

  • JavaScript是一种弱类型的语言,和一些强类型的语言如java,C#等不同。js的对象和他们的对象不一样,js的所有数据都是对象,普遍的对象就是若干键值对的组合。而在java,C#中是先有类,再通过类来创建实例,这个实例叫做对象。ps:在ES6中js推出了class这个概念(本人表示非常喜欢这样发展)。
  • js所有数据都是对象,言外之意就是:number,array,function,null,erro,reg,date,{一系列键值对的组合}等等数据类型都可以看作对象。
  • 我们有多种方式创建对象,new functionName或者直接var Myobject={一系列键值对}等等

下面我们就来用一个demo来看看吧

首先我们创建一个构造函数。这个构造函数类似于Java中类的构造函数,Js的构造函数的目的也是初始化值的作用,只是JS中无需先写class,直接写一个构造函数然后new这个构造函数就可以创建一个对象。

var person=function (name) {     this.name=name;     this.show=function () {         alert("我的名字是 "+this.name);     }}

我们可以看到构造函数里面有this,这个this.name就是指向这个函数中创建的name;符合我们之前的那句话,this指向当前对象。

我们再添加一段Html代码以显示效果

<body><div>    <div>        <button id="test">查看爱好</button>    </div></div><script type="text/javascript" src="index.js"></script></body>

然后我们为这个id为test的按钮绑定一个事件,此时js代码变为

var person=function (name) {     this.name=name;     this.show=function () {         alert("我的名字是 "+this.name);     }}var pengl=new person("PengL");document.getElementById("test").addEventListener("click",function() {    pengl.show();});

我们点击这个按钮可以看到

现在我们来分析一下代码,我们通过var pengl=new person("PengL");来创建了一个对象,此时this指向的便是PengL这个当前对象,然后将对象的show方法绑定为按钮的点击事件,点击按钮后通过弹出框可以看到this.name指向的名字。说明到现在为止。this,指向当前对象仍然是正确的。

那么问题来了,如果我们不用new创建对象,直接调用这个函数会怎么样呢。我们把代码改成这样

var person=function (name) {     this.name=name;     this.show=function () {         alert("我的名字是 "+this.name);     }}//var pengl=new person("PengL");var pengl=person("PengL");document.getElementById("test").addEventListener("click",function() {    pengl.show();});

然后运行点击按钮查看效果,这时我们发现没有反应,我们打开控制台调试看,发现报错

我们可以看到控制台下面显示show属性undefined,为什么会这样。明明在构造函数中定义了show的呀。然而实际情况是这样:我们这次没有新建对象,而是直接把函数赋给了变量PengL,这时我们需要考虑的问题是什么?当前对象是谁?是这个函数吗?函数不也是对象吗?事实是如果没有新建对象。那么这些普通函数都属于一个大对象,那就是document对象。所以现在this,指的是document对象。而document对象并没有定义show方法,所以此时报错show为undefined。

通过上面的例子我们可以看到,this指向当前对象并没有错,重点是我们得弄明白当前对象是哪个,比如我们直接在html代码中添加onclick事件

<button  value="aihao" onclick="GetButton(this.value)">    查看爱好</button>

这时这个this指向的便是button元素这个对象,this.value便可以获取button的value值。

如过在js中通过this来获取value值

function GetButton () { alert(this.value);  }

这时this指向的便是document对象,又会出现value undefined的错误了。


通过以上例子我们总结出以下几点:

  • 找准当前对象是谁
  • this指向当前对象
  • 创建对象后this才能指向这个对象,直接调用函数是没用的
  • 普通函数和全局变量等属于的对象都是document

这就是我想要分享的对于this的理解,可能有些同学对于js的对象这有一些不熟悉,看demo的时候会有些不容易理解,网上有很多大牛写的关于对象的博客。大家可以去看看。后面要是有机会,笔者也尝试看能不能把对象也来说说清楚!!!


1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 混合喂养宝宝不吃奶瓶怎么办 混合喂养的宝宝不吃奶粉怎么办 三个月的宝宝突然不吃奶粉怎么办 四个月宝宝拉绿屎推拿怎么办 三个月大的宝宝消化不良怎么办 婴儿大便常规正常的腹泻怎么办 宝宝十个月母乳不够吃怎么办 吃药上吐腹泻在3怎么办 房子买了新生儿户口怎么办 吃海鲜拉稀拉水怎么办 吃海鲜腹泻拉水怎么办 两个月宝宝不爱睡觉怎么办 2个月宝宝干呕怎么办 9个月宝宝拉稀的怎么办 宝宝拉肚子快一个月了怎么办 满月宝宝发烧38度怎么办 大人腹泻10天了怎么办 十个月宝贝拉水怎么办 十个月宝宝脱水哭闹怎么办 宝宝肠胃不好老是拉肚子怎么办 七个月的宝宝老是拉肚子怎么办 6个月宝宝拉肚子怎么办 没满月宝宝吐奶怎么办 八个月宝宝有点拉肚子怎么办 4个月宝宝拉水怎么办 2岁宝宝消化不好拉肚子怎么办 两岁宝宝老拉肚子怎么办 2岁宝宝拉肚子老不好怎么办 2岁宝宝一直拉肚子不好怎么办 5天新生儿拉稀水怎么办 4天新生儿拉稀水怎么办 新生儿40天拉稀水怎么办 一周多的宝宝拉肚子怎么办 出生半个月的宝宝拉肚子怎么办 刚出生几天的宝宝拉肚子怎么办 刚出生的宝宝拉肚子怎么办 出生八天的宝宝拉肚子怎么办 刚出生婴儿拉水怎么办 三岁宝宝一直吐怎么办 3岁宝宝一直吐怎么办 5个月宝宝一直吐怎么办