狗日的this

来源:互联网 发布:sql语言教程 编辑:程序博客网 时间:2024/04/28 10:47

本文主要总结自《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