深入理解javascript中的this


Posted in Javascript onFebruary 08, 2021

深入理解Js中的this

JavaScript作用域为静态作用域static scope,但是在Js中的this却是一个例外,this的指向问题就类似于动态作用域,其并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,当然实际上this的最终指向的是那个调用它的对象。

作用域

我们先来了解一下JavaScript的作用域,以便理解为什么说this更类似于动态作用域,通常来说,一段程序代码中所用到的名字并不总是有效或可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域scope,当一个方法或成员被声明,他就拥有当前的执行上下文context环境,在有具体值的context中,表达式是可见也都能够被引用,如果一个变量或者其他表达式不在当前的作用域,则将无法使用。作用域也可以根据代码层次分层,以便子作用域可以访问父作用域,通常是指沿着链式的作用域链查找,而不能从父作用域引用子作用域中的变量和引用。
JavaScript作用域为静态作用域static scope,也可以称为词法作用域lexical scope,其主要特征在于,函数作用域中遇到既不是参数也不是函数内部定义的局部变量时,去函数定义时上下文中查,而与之相对应的是动态作用域dynamic scope则不同,其函数作用域中遇到既不是参数也不是函数内部定义的局部变量时,到函数调用时的上下文中去查。

var a = 1;
var s = function(){
 console.log(a);
};
(function(){
 var a = 2;
 s(); // 1
})();

调用s()是打印的a为1,此为静态作用域,也就是声明时即规定作用域,而假如是动态作用域的话在此处会打印2。现在大部分语言都采用静态作用域,比如CC++JavaPHPPython等等,具有动态作用域的语言有Emacs LispCommon LispPerl等。

全局作用域

直接声明在顶层的变量或方法就运行在全局作用域,借用函数的[[Scopes]]属性来查看作用域,[[Scopes]]是保存函数作用域链的对象,是函数的内部属性无法直接访问但是可以打印来查看。

function s(){}
console.dir(s);
/*
 ...
 [[Scopes]]: Scopes[1]
 0: Global ...
*/
// 可以看见声明的s函数运行的上下文环境是全局作用域

函数作用域

当声明一个函数后,在函数内部声明的方法或者成员的运行环境就是此函数的函数作用域

(function localContext(){
 var a = 1;
 function s(){ return a; }
 console.dir(s);
})();
/*
 ...
 [[Scopes]]: Scopes[2]
 0: Closure (localContext) {a: 1}
 1: Global ...
*/
// 可以看见声明的s函数运行的上下文环境是函数localContext的作用域,也可以称为局部作用域

块级作用域

代码块内如果存在let或者const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。

{
 let a = 1;
 function s(){return a;}
 console.dir(s);
 /*
 ...
 [[Scopes]]: Scopes[2]
 0: Block {a: 1}
 1: Global ...
 */
}
// 可以看见声明的s函数运行的上下文环境是Block块级作用域,也是局部作用域

分析

我们在使用this之前有必要了解为什么在JavaScript中要有this这个设计,在这之前我们先举个小例子,通常我们使用this时可能会遇到的典型问题就类似于下面这样,虽然我们运行的都是同一个函数,但是执行的结果可能会不同。

var obj = {
 name: 1,
 say: function() {
 return this.name;
 }
};
window.name = 2;
window.say = obj.say;

console.log(obj.say()); // 1
console.log(window.say()); // 2

产生这样的结果的原因就是因为使用了this关键字,前文已经提到了this必须要在运行时才能确定,在这里,对于obj.say()来说,say()运行的环境是obj对象,对于window.say()来说,say()运行的环境是window对象,所以两者运行的结果不同。
此时我们就来了解一下,为什么JavaScript会有this这样一个设计,我们首先来了解一下JavaScript的内存结构中的堆栈,堆heap是动态分配的内存,大小不定也不会自动释放,栈stack为自动分配的内存空间,在代码执行过程中自动释放。JavaScript在栈内存中提供一个供Js代码执行的环境,关于作用域以及函数的调用都是栈内存中执行的。Js中基本数据类型StringNumberBooleanNullUndefinedSymbol,占用空间小且大小固定,值直接保存在栈内存中,是按值访问,对于Object引用类型,其指针放置于栈内存中,指向堆内存的实际地址,是通过引用访问。
那么此时我们来看一下上边的示例,在内存中对于obj对象是存放在堆内存的,如果在对象中的属性值是个基本数据类型,那么其会跟这个对象存储在同一块内存区域,但是这个属性值同样可能是一个引用类型,那么对于say这个函数也是存在于堆内存中的,实际上在此处我们可以将其理解为这个函数的实际定义在一个内存区域(以一个匿名函数的形式存在),而obj这个对象同样在其他的一个内存区域,obj通过say这个属性指向了这个匿名函数的内存地址,obj --say--> funtion,那么此时问题来了,由于这种内存结构,我们可以使任何变量对象等指向这个函数,所以在JavaScript的函数中是需要允许我们取得运行环境的值以供使用的,我们必须要有一种机制,能够在函数体内部获得当前的运行环境context,所以this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

使用

我们需要记住,this是在运行时进行绑定的,并不是在定义时绑定,它的context取决于函数调用时的各种条件,简单来说this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式,再简单来说this永远指向调用者,但箭头函数除外,接下来我们介绍一下五种this的使用情况。

默认绑定

最常用的函数调用类型即独立函数调用,这个也是优先级最低的一个,此时this指向全局对象,注意如果使用严格模式strict mode,那么全局对象将无法使用默认绑定,因此this会变为undefined

var a = 1; // 变量声明到全局对象中
function f1() {
 return this.a;
}

function f2() {
 "use strict";
 return this;
}

console.log(f1()); // 1 // 实际上是调用window.f1()而this永远指向调用者即window
console.log(f2()); // undefined // 实际上是调用 window.f2() 此时由于严格模式use strict所以在函数内部this为undefined

隐式绑定

对象属性引用链中只有最顶层或者说最后一层会影响this,同样也是this永远指向调用者,具体点说应该是指向最近的调用者,当然箭头函数除外,另外我们可能有意无意地创建间接引用地情况,这个情况下同样也适用于this指向调用者,在上文分析那部分使用的示例就属于间接引用的情况。

function f() {
 console.log(this.a);
}
var obj1 = {
 a: 1,
 f: f
};
var obj2 = {
 a: 11,
 obj1: obj1
};
obj2.obj1.f(); // 1 // 最后一层调用者即obj1
function f() {
 console.log(this.a);
}
var obj1 = {
 a: 1,
 f: f
};
var obj2 = {
 a: 11,
};
obj2.f = obj1.f; // 间接引用
obj2.f(); // 11 // 调用者即为obj2

显示绑定

如果我们想把某个函数强制在某个环境即对象上,那么就可以使用applycallbind强制绑定this去执行即可,每个Function对象都存在apply()call()bind()方法,其作用都是可以在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域,此外需要注意使用bind绑定this的优先级是大于applycall的,即使用bind绑定this后的函数使用applycall是无法改变this指向的。

window.name = "A"; // 挂载到window对象的name
document.name = "B"; // 挂载到document对象的name
var s = { // 自定义一个对象s
 name: "C"
}

var rollCall = {
 name: "Teacher",
 sayName: function(){
 console.log(this.name);
 }
}
rollCall.sayName(); // Teacher

// apply
rollCall.sayName.apply(); // A // 不传参默认绑定window
rollCall.sayName.apply(window); // A // 绑定window对象
rollCall.sayName.apply(document); // B // 绑定document对象
rollCall.sayName.apply(s); // C // 绑定自定义对象

// call
rollCall.sayName.call(); // A // 不传参默认绑定window
rollCall.sayName.call(window); // A // 绑定window对象
rollCall.sayName.call(document); // B // 绑定document对象
rollCall.sayName.call(s); // C // 绑定自定义对象

// bind // 最后一个()是为让其执行
rollCall.sayName.bind()(); //A // 不传参默认绑定window
rollCall.sayName.bind(window)(); //A // 绑定window对象
rollCall.sayName.bind(document)(); //B // 绑定document对象
rollCall.sayName.bind(s)(); // C // 绑定自定义对象

new绑定

JavaScriptnew是一个语法糖,可以简化代码的编写,可以批量创建对象实例,在new的过程实际上进行了以下操作。

创建一个空的简单JavaScript对象即{}。链接该对象(即设置该对象的构造函数)到另一个对象。将步骤1新创建的对象作为this的上下文context。如果该函数没有返回对象,则返回步骤1创建的对象。

function _new(base,...args){
 var obj = {};
 obj.__proto__ = base.prototype;
 base.apply(obj, args);
 return obj;
}

function Funct(a) {
 this.a = a;
}
var f1 = new Funct(1);
console.log(f1.a); // 1

var f2 = _new(Funct, 1);
console.log(f2.a); // 1

箭头函数

箭头函数没有单独的this,在箭头函数的函数体中使用this时,会取得其上下文context环境中的this。箭头函数调用时并不会生成自身作用域下的this,它只会从自己的作用域链的上一层继承this。由于箭头函数没有自己的this指针,使用applycallbind仅能传递参数而不能动态改变箭头函数的this指向,另外箭头函数不能用作构造器,使用new实例化时会抛出异常。

window.name = 1;
var obj = {
 name: 11,
 say: function(){
 const f1 = () => {
 return this.name;
 }
 console.log(f1()); // 11 // 直接调用者为window 但是由于箭头函数不绑定this所以取得context中的this即obj对象
 const f2 = function(){
 return this.name;
 }
 console.log(f2()); // 1 // 直接调用者为window 普通函数所以
 return this.name;
 }
}

console.log(obj.say()); // 11 // 直接调用者为obj 执行过程中的函数内context的this为obj对象

示例

function s(){
 console.log(this);
}

// window中直接调用 // 非 use strict
s(); // Window // 等同于window.s(),调用者为window
// window是Window的一个实例 // window instanceof Window //true

// 新建对象s1
var s1 = {
 t1: function(){ // 测试this指向调用者
 console.log(this); // s1
 s(); // Window // 此次调用仍然相当 window.s(),调用者为window
 },
 t2: () => { // 测试箭头函数,this并未指向调用者
 console.log(this);
 },
 t3: { // 测试对象中的对象
 tt1: function() {
 console.log(this);
 } 
 },
 t4: { // 测试箭头函数以及非函数调用this并未指向调用者
 tt1: () => {
 console.log(this);
 } 
 },
 t5: function(){ // 测试函数调用时箭头函数的this的指向,其指向了上一层对象的调用者
 return {
 tt1: () => {
 console.log(this);
 }
 }
 }
}
s1.t1(); // s1对象 // 此处的调用者为 s1 所以打印对象为 s1
s1.t2(); // Window
s1.t3.tt1(); // s1.t3对象
s1.t4.tt1(); // Window
s1.t5().tt1(); // s1对象

到此这篇关于深入理解Js中的this的文章就介绍到这了,更多相关深入理解Js中的this内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
JS获取鼠标坐标位置实例分析
Jan 20 Javascript
jQuery Tags Input Plugin(添加/删除标签插件)详解
Jun 20 Javascript
bootstrap模态框垂直居中效果
Dec 03 Javascript
一个例子轻松学会Vue.js
Jan 02 Javascript
jquery实现自定义图片裁剪功能【推荐】
Mar 08 Javascript
原生JS实现隐藏显示图片 JS实现点击切换图片效果
Jan 27 Javascript
jQuery获取随机颜色的实例代码
May 21 jQuery
jQuery开发仿QQ版音乐播放器
Jul 10 jQuery
浅谈javascript事件环微任务和宏任务队列原理
Sep 12 Javascript
关于IDEA中的.VUE文件报错 Export declarations are not supported by current JavaScript version
Oct 17 Javascript
如何使用gpu.js改善JavaScript的性能
Dec 01 Javascript
JS如何监听div的resize事件详解
Dec 03 Javascript
Vue中使用wangeditor富文本编辑的问题
Feb 07 #Vue.js
vue使用lodop打印控件实现浏览器兼容打印的方法
Feb 07 #Vue.js
js基于canvas实现时钟组件
Feb 07 #Javascript
nestjs中异常过滤器Exceptionfilter的具体使用
Feb 07 #Javascript
js实现类选择器和name属性选择器的示例步骤
Feb 07 #Javascript
vue如何使用rem适配
Feb 06 #Vue.js
如何管理Vue中的缓存页面
Feb 06 #Vue.js
You might like
饭制《星际争霸》Mod:优化游戏机制 增加新单位
2017/07/02 星际争霸
PHP树的代码,可以嵌套任意层
2006/10/09 PHP
php下关于Cannot use a scalar value as an array的解决办法
2010/08/08 PHP
PHP和JavaScrip分别获取关联数组的键值示例代码
2013/09/16 PHP
PHP_NETWORK_GETADDRESSES: GETADDRINFO FAILED问题解决办法
2014/05/04 PHP
PHP伪造来源HTTP_REFERER的方法实例详解
2015/07/06 PHP
PHP环境搭建的详细步骤
2016/06/30 PHP
ThinkPHP发送邮件示例代码
2016/10/08 PHP
laravel 事件/监听器实例代码
2019/04/12 PHP
初学JavaScript第二章
2008/09/30 Javascript
JS限制上传图片大小不使用控件在本地实现
2012/12/19 Javascript
很实用的js选项卡切换效果
2016/08/12 Javascript
真正好用的js验证上传文件大小的简单方法
2016/10/27 Javascript
AngularJS实现用户登录状态判断的方法(Model添加拦截过滤器,路由增加限制)
2016/12/12 Javascript
windows下vue-cli及webpack搭建安装环境
2017/04/25 Javascript
jQuery实现QQ空间汉字转拼音功能示例
2017/07/10 jQuery
原生js封装添加class,删除class的实例
2017/11/06 Javascript
9种改善AngularJS性能的方法
2017/11/28 Javascript
js中Object.defineProperty()方法的不详解
2018/07/09 Javascript
Vue中用JSON实现刷新界面不影响倒计时
2020/10/26 Javascript
javascript中导出与导入实现模块化管理教程
2020/12/03 Javascript
[41:12]Liquid vs Secret 2019国际邀请赛淘汰赛 败者组 BO3 第一场 8.24
2019/09/10 DOTA
[01:07:20]DOTA2-DPC中国联赛 正赛 Dynasty vs XG BO3 第二场 2月2日
2021/03/11 DOTA
深入解析Python编程中JSON模块的使用
2015/10/15 Python
docker-py 用Python调用Docker接口的方法
2019/08/30 Python
PyCharm2020.3.2安装超详细教程
2021/02/08 Python
全球性的在线时尚男装零售商:boohooMAN
2016/12/17 全球购物
STAUD官方网站:洛杉矶独有的闲适风格
2019/04/11 全球购物
索尼巴西商店:Sony巴西
2019/06/21 全球购物
Java如何获得ResultSet的总行数
2016/09/03 面试题
师范生自荐信范文
2013/10/06 职场文书
师范学院毕业生求职信范文
2013/12/26 职场文书
护士进修自我鉴定
2014/02/07 职场文书
税务干部鉴定材料
2014/02/11 职场文书
保研推荐信范文
2015/03/25 职场文书
JS前端轻量fabric.js系列物体基类
2022/08/05 Javascript