JS 中 this 指向问题了解多少?

发布于 2023-09-27 00:42:52 字数 8319 浏览 70 评论 0

关于 this 指针的研究

基础实例说明

实例 1:

<script>
    var name = "Kevin Yang";
    function sayHi(){
        console.log("你好,我的名字叫" + this.name);
    }
    sayHi()
</script>

如果在 html 端, 这个 this.name 是可以调用全局对象 name 的, 这个 this 实际上是指向的 window 的, var 也是把变量挂在到 window 对象上面的。

但是同样的这个实例如果放在 node 端,就是一个 undefined ,原因是 node 端没有 window 对象。

实例 2:

var name = "Kevin Yang";   
function sayHi(){     
    console.log("你好,我的名字叫" + this.name);   
}   
var person = {};   
person.sayHello = sayHi;   
person.sayHello(); 

这一次打招呼的内容就有点无厘头了,我们发现 this.name 已经变成 undefined 了。这说明,在 sayHello 函数内部执行时已经找不着 this.name 对象了。,原因是这儿时候,this 指向的 person 对象,但是 this 对象上面是没有 name 属性的。
如果改为这样 var person = {name:"Marry"}; 就可以得到我们想要的内容了。

判别 this 指针的指导性原则

在 Javascript 里面,this 指针代表的是执行当前代码的对象的所有者。

在上面的示例中我们可以看到,第一次,我们定义了一个全局函数对象 sayHi 并执行了这个函数,函数内部使用了 this 关键字,那么执行 this 这 行代码的对象是 sayHi(一切皆对象的体现),sayHi 是被定义在全局作用域中。其实在 Javascript 中所谓的全局对象,无非是定义在 window 这个根对象下的一个属性而已。

因此,sayHi 的所有者是 window 对象。也就是说,在全局作用域下,你可以通过直接使用 name 去引用这 个对象,你也可以通过 window.name 去引用同一个对象。因而this.name 就可以翻译为 window.name 了

再来看第二个 this 的示例。第一次,person 里面没有 name 属性,因此弹 出的对话框就是 this.name 引用的就是 undefined 对象(Javascript 中所有只声明而没有定义的变量全都指向 undefined 对象);

而第二次我们在定义 person 的时候加了 name 属性了,那么 this.name 指向的自然就是我们定义的字符串了。

理解了上面所说的之后,我们将上面最后一段示例改造成面向对象式的代码。

var name = "Kevin Yang";   
function sayHi(){     
    console.log("你好,我的名字叫" + this.name);   
}  
function Person(name){     
    this.name = name;   
}   
Person.prototype.sayHello = sayHi;   
var marry = new Person("Marry");     
marry.sayHello();   
var kevin = new Person("Kevin");   
kevin.sayHello(); 

容易误用的情况

示例 1——内联式绑定 Dom 元素的事件处理函数

<body>
<input type="button" value="点击我" onclick="sayHi()">
<script type="text/javascript">   
	function sayHi(){     
		alert("当前点击的元素是" + this.tagName);  
	}   
</script> 
</body>

在此例代码中,我们绑定了 button 的点击事件,期望在弹出的对话框中打印出点击元素的标签名。但运行结果却是: 当前点击的元素是 undefined

也就是 this 指针并不是指向 input 元素。这是因为当使用内联式绑定 Dom 元素的事件处理函数时,实际上相当于执行了以下代码:

在这种情况下 sayHi 函数对象的所有权并没有发生转移,还是属于 window 所有。用上面的指导原则一套我们就很好理解为什么 this.tagName 是 undefined 了。

那么如果我们要引用元素本身怎么办呢?

我们知道,onclick 函数是属于 btnTest 元素的,那么在此函数内部,this 指针正是指向此 Dom 对象,于是我们只需要把 this 作为参数传入 sayHi 即可。

<input type="button" value="点击我" onclick="sayHi(this)">
<script type="text/javascript">   
	function sayHi(el){     
		alert("当前点击的元素是" + el.tagName);   } 
</script> 

等价代码如下:

<script type="text/javascript">    
	document.getElementById("btnTest").onclick = function(){     sayHi(this);   } 
</script>

示例 2——临时变量导致的 this 指针丢失

<script type="text/javascript">   
	var Utility = {     
		decode:function(str){       return unescape(str);     },     
		getCookie:function(key){       
			// ... 省略提取 cookie 字符串的代码       
			var value = "i%27m%20a%20cookie";       
			return this.decode(value);     
		}   
	};   
	console.log(Utility.getCookie("identity")) 
</script>

一般都会自己封装一个 Utility 的类,然后将一些常用的函数作为 Utility 类的属性,如客户端经常会 用到的 getCookie 函数和解码函数。

如果每个函数都是彼此独立的,那么还好办,问题是,函数之间有时候会相互引用。例如上面的 getCookie 函 数,会对从 document.cookie 中提取到的字符串进行 decode 之后再返回。如果我们通过 Utility.getCookie 去调用的话,那 么没有问题,我们知道,getCookie 内部的 this 指针指向的还是 Utility 对象,而 Utility 对象时包含 decode 属性的。代码可以成 功执行。

但是有个人不小心这样使用 Utility 对象呢?

<script type="text/javascript">   
	function showUserIdentity(){     
		// 保存 getCookie 函数到一个局部变量,因为下面会经常用到     
		var getCookie = Utility.getCookie;     
		alert(getCookie("identity"));   
	}   
	showUserIdentity(); 
</script>

这个时候运行代码会抛出异常“this.decode is not a function”。
运用上面我们讲到的指导原则,很好理解,因为此时 Utility.getCookie 对象被赋给了临时变量 getCookie,而临 时变量是属于 window 对象的——只不过外界不能直接引用,只对 Javascript 引擎可见——于是在 getCookie 函数内部的 this 指针指向 的就是 window 对象了,而 window 对象没有定义一个 decode 的函数对象,因此就会抛出这样的异常来。

这个问题是由于引入了临时变量导致的 this 指针的转移。解决此问题的办法有几个:
不引入临时变量,每次使用均使用 Utility.getCookie 进行调用 getCookie 函数内部使用 Utility.decode 显式引用 decode 对象而不通过 this 指针隐式引用(如果 Utility 是一个实例化的对象,也即是通过 new 生成的,那么此法不可用)使用 Funtion.apply 或者 Function.call 函数指定 this 指针

第三种使用 apply 和 call 修正的办法实例如下:

<script type="text/javascript">   
	function showUserIdentity(){     
		// 保存 getCookie 函数到一个局部变量,因为下面会经常用到     
		var getCookie = Utility.getCookie;     
		alert(getCookie.call(Utility,"identity"));     
		alert(getCookie.apply(Utility,["identity"]));   
	}   
	showUserIdentity(); 
</script>

示例 3——函数传参时导致的 this 指针丢失

<script type="text/javascript">   
	var person = {     
		name:"Kevin Yang",     
		sayHi:function(){       
			alert("你好,我是"+this.name);     
		}   
	}   
	setTimeout(person.sayHi,5000); 
</script>

这段代码期望在访客进入页面 5 秒钟之后向访客打声招呼。setTimeout 函数接收一个函数作为参数,并在指定的触发时刻执行这个函数。

可是,当我们等了 5 秒钟之后,弹出的对话框显示的 this.name 却是 undefined。

其实这个问题和上一个示例中的问题是类似的,都是因为临时变量而导致的问题。
当我们执行函数的时候,如果函数带有参数,那么这个时候 Javascript 引擎会创建一个临时变量,并将传入的参数复制(注意,Javascript 里面都是值传递的,没有引用传递的概念)给此临时变量。

也就是说,整个过程就跟上面我们定义了一个 getCookie 的临时变量,再将 Utility.getCookie 赋值给这个临时变量一样。只不过在这个示例中,容易忽视临时变量导致的 bug。

函数对象传参

Prototype 的解决方案——传参之前使用 bind 方法将函数封装起来,并返回封装后的对象

<script type="text/javascript">   
	var person = {    
		name:"Kevin Yang",     
		sayHi:function(){       
			alert("你好,我是"+this.name);     
		}   
	}   
	var boundFunc = person.sayHi.bind(person,person.sayHi);   
	setTimeout(boundFunc,5000); 
</script>

bind 方法的实现其实是用到了 Javascript 又一个高级特性——闭包。我们来看一下源代码:

function bind(){  
	if (arguments.length < 2 && arguments[0] === undefined)      
		return this;   
	var __method = this, args = $A(arguments), object = args.shift();   
	return function(){     return __method.apply(object, args.concat($A(arguments)));   } 
}

首先将 this 指针存入函数内部临时变量,然后在返回的函数对象中引用此临时变量从而形成闭包。

变化的 this

在 JavaScript 中,this 通常 指向的是我们正在执行的函数本身,或者是指向该函数所属的对象(运行时)。
当我们在页面中定义了函数 doSomething()的时候,它的 owner 是页面,或者是 JavaScript 中的 window 对象(或 global 对象)。
对于一个 onclick 属性,它为它所属的 HTML 元素所拥有,this 应该指向该 HTML 元素。

在几种常见场景中 this 的变化

function doSomething () { 
   alert(this.navigator); //appCodeName 
   this.value = "I am from the Object constructor"; 
   this.style.backgroundColor = "# 000000"; 
} 
  • 作为普通函数直接调用时,this 指向 window 对象.
  • 作为控件事件触发时
    • inline event registration 内联事件注册 .将事件直接写在 HTML 代码中(), 此时 this 指向 window 对象 。
    • Traditional event registration 传统事件注册 (DHTML 方式). 形如 element.onclick = doSomething; 此时 this 指向 element 对象
    • 作为参数传递可以指向 element
  • 作为对象使用时 this 指向当前对象。形如:new doSomething();
  • 使用 apply 或者 call 方法时,this 指向所传递的对象。 形如:var obj={}; doSomething.apply(obj,new Array(”nothing”));

接下来文章中我们将要讨论的问题是:在函数 doSomething()中 this 所指的是什么?

function doSomething() { 
    this.style.color = '#cc0000'; 
} 

在 JavaScript 中,this 通常指向的是我们正在执行的函数本身(译者注:用 owner 代表 this 所指向的内容),或者是,指向该函数所属的对 象。
当我们在页面中定义了函数 doSomething()的时候,它的 owner 是页面,或者是 JavaScript 中的 window 对象(或 global 对象)。
对于一个 onclick 属性,它为它所属的 HTML 元素所拥有,this 应该指向该 HTML 元素。
这种“所有权”就是 JavaScript 中面向对象的一种方式。在 Objects as associative arrays 中可以查看一些更多的信息。

11_02

总结

怎样在一个代码环境中快速的找到 this 所指的对象呢?

  • 1、 要清楚的知道对于函数的每一步操作是拷贝还是引用(调用)
  • 2、 要清楚的知道函数的拥有者(owner)是什么
  • 3、 对于一个 function,我们要搞清楚我们是把它当作函数使用还是在当作类使用

补充一个图:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

萌酱

暂无简介

文章
评论
26 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文