摘要:全局上下文在全局中,一律指向全局对象。特殊的以下情况中的需要进行特殊记忆。构造函数当一个函数作为构造函数使用时,构造函数的指向由该构造函数出来的对象。举例使用绑定时,监听函数中的指向触发事件的,表示被绑定了监听函数的元素。执行与执行同理。
</>复制代码
我的博客地址 → this | The story of Captain,转载请注明出处。
问:this 是什么?
答:this 是 call 方法的第一个参数,call 的第一个参数就是 this。
完。
就这么简单么?是的。
为什么这样说?因为所有的函数/方法调用的时候都可以 转换 为 call 形式,call 的第一个参数显式的指明了函数该次执行时候的上下文。
今天我们深入探讨一下如何确定 this。
如何确定 this ?this 由函数的上下文确定。
如何确定“上下文” ?上下文分为 全局上下文(Global Context) 以及 函数上下文(Function Context)。
全局上下文在全局中,this 一律指向 全局对象 window。例如:
</>复制代码
console.log(this === window); //; true
函数上下文
在函数中,上下文由函数被调用的方式决定。
以 “函数名( )” 形式调用的函数即为简单调用,简单调用时上下文为全局上下文,因此 this === window 。
举例一:
</>复制代码
function foo () {
console.log(this === window);
}
foo(); // true
举例二:
</>复制代码
function fn1 () {
function fn2 () {
console.log(this === window);
}
fn2();
}
fn1(); // true,因为 fn2 为简单调用
举例三:
</>复制代码
let obj = {
fn1: function () {
console.log(this === window);
}
};
let fn2 = obj.fn1;
fn2(); // true
第三个例子中,为什么 fn2() 执行结果为 true ?因为执行了 let fn2 = obj.fn1 之后 fn2 为:
</>复制代码
fn2 = function () {
console.log(this);
}
再执行 fn2() 时,为简单调用,因此 this === window 。
当函数作为一个对象的方法被调用时,this 指向该对象。
举例一:
</>复制代码
let obj = {
fn1: function () {
console.log(this === obj);
}
};
obj.fn1(); // true
以 obj.fn1() 形式调用 fn1 时,是以方法形式调用的,this 指向该函数所属的对象,即 obj。
举例二:
</>复制代码
let obj = {
fn1: {
fn2:function () {
console.log(this === obj.fn1);
}
}
};
obj.fn1.fn2(); // true
以 obj.fn1.fn2() 形式调用 fn2 时,是以方法形式调用的,this 指向该函数所属的对象,即 obj.fn1,很多人常误以为此处的 this 指向 obj,这是错误的。
举例三:
</>复制代码
let obj = {
fn1: function () {
return function () {
console.log(this === window);
}
}
};
let fn2 = obj.fn1();
fn2(); // true
为什么 fn2() 的执行结果为 true ?因为执行了 let fn2 = obj.fn1() 之后 fn2 为:
</>复制代码
fn2 = function () {
console.log(this === window);
}
再执行 fn2() 时,为简单调用,因此 this === window 。如果想要将 fn2 中的 this 指向 obj,可将指向 obj 的 this 保存在中间变量,改动如下所示:
</>复制代码
let obj = {
fn1: function () {
let that = this;
return function () {
console.log(that === obj);
}
}
};
let fn2 = obj.fn1();
fn2(); // true
利用 let that = this 将 fn1 中的 this 保存在 that 变量中,然后 fn2() 的结果即为 true,当然这其中涉及到了 闭包(closure) 的知识。
特殊的 this以下情况中的 this 需要进行特殊记忆。
箭头函数箭头函数(arrow function,=>),箭头函数为 ES6 中引入的新的函数表示法,不同之处在于,箭头函数中没有 this,箭头函数中的 this 为其执行上下文中的 this,如何理解?举例说明。
举例一:
</>复制代码
() => console.log(this === window); // true
其执行上下文为全局上下文,this 指向 window。
举例二:
</>复制代码
function foo () {
return () => console.log(this === window);
};
foo()(); // true
和方法调用中的举例三类似。
举例三:
</>复制代码
let obj = {
fn1: () => console.log(this === window);
};
obj.fn1(); // true
为什么是 true ?方法调用中的举例一中的 this 不是 obj 吗?没错,箭头函数 fn1 中是没有自己的 this 的,因此 this 不指向 obj ,继续向上找 obj 的上一级,直到找到有 this 的上下文为止,obj 处在全局上下文中, 全局上下文中有 this,因此箭头函数中的 this 为全局上下文中的 this,即 指向 window。
举例四:
</>复制代码
let obj = {
fn1: function () {
return () => console.log(this === obj);
}
};
let fn2 = obj.fn1();
fn2(); // true
此处又和方法调用的举例三不同,因为箭头函数中是没有自己的 this 的,箭头函数中的 this 为其上一级的 this ,因此,箭头函数中的 this 为其上一级,即 fn1 中的 this,fn1 中的 this 指向 obj,所以箭头函数中的 this 指向 obj。根据箭头函数的特性:箭头函数中的 this 保留了其上一级的 this 指向,那么方法调用举例三的改动可以优化为本例所示,用一个箭头函数即可解决,省去了中间变量。
构造函数当一个函数作为构造函数使用时,构造函数的 this 指向由该构造函数 new 出来的对象。举例说明:
</>复制代码
function CreateNewPerson (name,gender,age) {
this.name = name;
this.gender = gender;
this.age = age;
}
let me = new CreateNewPerson("daijt","male",18);
console.log(me.name); // "daijt"
console.log(me.gender); // "male"
console.log(me.age); // 18
执行 let me = new CreateNewPerson("daijt","male",18) 时,构造函数中的 this 直接指向由其 new 出来对象对象 me ,因此执行完该句后 me 的结构如下:
</>复制代码
me = {
name: "daijt",
gender: "male",
age: 18
}
原型链
举例一:
</>复制代码
let name = new String("daijt");
name.toUpperCase(); // DAIJT
根据上文构造函数中的 this,执行 let name = new String("daijt") 时,String 构造函数中的 this 指向了 name,而 name 有 __proto__ 属性,该属性指向所有 string 类的共有属性或者方法,而这些共有的属性和方法都保存在 String.prototype 中,即:
</>复制代码
name.__proto__ === String.prototype; // true
因此 name 是有 toUpperCase 方法的(原型链继承而来),调用 toUpperCase 时,toUpperCase 中的 this 指向 name,因此 name.toUpperCase() 的结果为 DAIJT 。
举例二:
</>复制代码
let name = "daijt";
name.toUpperCase.(); // DAIJT
为何没有通过 new 出来的对象也具有 toUpperCase 方法呢?因为在执行 let name = "daijt" 的过程中,JS 有一个临时转化的过程,例如:
</>复制代码
let name = (function (string) {
return new String(string);
})("daijt");
因此,name 也继承了 string 类共有的属性和方法,这也算是 JS 的一个语法糖吧。 当然,这涉及到了其他的知识。
DOM EventHandle举例:
</>复制代码
let buttons = document.querySelector("button");
buttons.addEventListener("click", function (event) {
console.log(this === event.currentTarget); // true
});
使用 addEventListener 绑定 DOM 时,监听函数中的 this 指向触发事件的 currentTarget,currentTarget 表示被绑定了监听函数的 DOM 元素。
</>复制代码
注意:如果是通过冒泡触发监听函数的话,event.target 不一定等于 event.currentTarget 。
jQuery EventHandle
HTML:
</>复制代码
- father-ul的第1个li
- father-ul的第2个li
- son-ul的第1个li
- son-ul的第2个li
- son-ul的第3个li
- father-ul的第3个li
JavaSctipt:
</>复制代码
$("#father-ul").on("click", ".father-li", function (event) {
console.log(event.target);
console.log(event.currentTarget);
console.log(this === currentTarget);
});
当点击
</>复制代码
father-ul的第1个li
father-ul的第1个li
true
当点击
</>复制代码
son-ul的第2个li
father-ul的第2个li
-
- son-ul的第1个li
-
- son-ul的第2个li
-
- son-ul的第3个li
-
true
因此可以得出结论:jQuery EventHandle 中的 this 指的是被代理事件监听的 DOM 元素,也就是匹配所有选择器的 DOM 元素,即 .father-li ,具体解释可参照 jQuery 文档 。
### 如何改变 this
以上所述的 this 都为确定的 this,那么如何自己设置 this,改变 this 的指向呢?或者说如何动态改变上下文呢?ES5 为我们提供了三个全局方法:call()、apply()、bind()。三个方法都可以动态的改变上下文,即 this 的指向,三者的区别可以参照 MDN,以 call() 为例进行说明。
</>复制代码
var name = "全局上下文";
let me = {
name: "daijt",
gender: "male".
age: 23,
};
let myGirlFriend = {
name: "xiaofang",
gender: "female",
age: 18
};
function printName() {
console.log(this.name);
}
printName(); // window
printName.call(me); // daijt
printName.call(myGirlFriend); // xiaofang
执行 printName() 时:
简单调用,因此其内部的 this 指向 全局上下文,因此 this === window ,而使用 var 关键字在全局声明的变量会作为 window 对象的属性,因此 this.name === window.name === 全局上下文 。
执行 printName.call(me) 时:
因为 call() 的第一个参数为 thisArg ,因此使用 call() 显式的指明了 printName 函数本次执行的上下文,即 me,因 this 指向上下文,所以 this === me ,this.name === me.name === daijt 。
执行 printName.call(myGirlFriend) 与执行 printName.call(me) 同理。
技巧回到本文开头,所有的函数/方法调用的时候都可以 转换 为 call 形式,call 的第一个参数显式的指明了函数该次执行时候的上下文,这就是判断 this 指向的技巧,以代码为例进行演示:
举例一:
</>复制代码
function foo () {
console.log(this);
}
foo(); // window
foo.call(); // window
// non-strict mode
foo.call(undefined); // window
// strict mode
foo.call(undefined); // undefined
foo() 为简单调用,因此 this === window 。
foo.call() 中,call() 的第一个参数未指明,那么 this === window ,在全局上下文中,非严格模式 下,undefined 即为 window ,严格模式 下,undefined 不能指代 window ,所以严格模式下 this === undefined 。
举例二:
</>复制代码
let obj = {
fn1: function () {
console.log(this === obj);
}
};
obj.fn1(); // true
obj.fn1.call(obj); // true
举例三:
</>复制代码
let obj = {
fn1: {
fn2:function () {
console.log(this === obj.fn1);
}
}
};
obj.fn1.fn2(); // true
obj.fn1.fn2.call(obj.fn1); // true
举例四:
</>复制代码
let obj = {
fn1: function () {
return function () {
console.log(this === window);
}
}
};
let fn2 = obj.fn1();
fn2(); // true
fn2.call(); // true
obj.fn1.call(obj).call(undefined); // true
以上三个例子中,如何判断传给 call() 的 this 呢?以举例四的最后一句代码为例进行分析:
通过这张 call() 的图解,this 应该完全掌握了,所以将函数的调用改写为 call() 形式是最直接明了判断 this 的方法。
看到这里,你搞懂 this 了吗?
</>复制代码
参考链接:
this - JavaScript | MDN
this 的值到底是什么?一次说清楚
你怎么还没搞懂 this ?
</>复制代码
更多精彩内容,请点击我的博客 → The story of Captain
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/92846.html
摘要:流的类型中有四种基本的流类型可读的流例如可写的流例如可读写的流例如在读写过程中可以修改和变换数据的流例如可读流可读流有两种模式流动模式可读流自动读取数据,通过接口的事件尽快将数据提供给应用。 流的简介 流(stream)在 Node.js 中是处理流数据的抽象接口(abstract interface)。 stream 模块提供了基础的 API 。使用这些 API 可以很容易地来构建实...
摘要:原生写的轮播兼容移动端插件,支持轮播速度,轮播内容,轮播间隔,手势灵敏度自定义,导航圆点点击跳转手势滑动。使用说明文件包含小部分语法编写的文件,在移动端有兼容性问题,仅供于源码参考。移动端跟端开发引用文件直接下载进行引入使用。 slide.js 原生js写的轮播兼容 pc+移动端 插件,支持轮播速度,轮播内容,轮播间隔,手势灵敏度自定义,导航圆点点击跳转,手势滑动。 使用说明:sli...
摘要:绑定轮播事件然后是鼠标移入移出事件的绑定鼠标移入移出事件移入时停止轮播播放的定时器,移出后自动开始下一张的播放。 通过上一篇文章的学习,我们基本掌握了一个轮子的封装和开发流程。那么这次将带大家开发一个更有难度的项目——轮播图,希望能进一步加深大家对于面向对象插件开发的理解和认识。 So, Lets begin! 目前项目使用 ES5及UMD 规范封装,所以在前端暂时只支持标签的引入方式...
摘要:本文包括简单的数据结构和查找算法,属于个人整理。初学编程可以用这里的东西联系一下,看一看也挺有意思博主个人不认为中算法数据结构不重要,毕竟这是程序开发的基本功。 本文包括简单的数据结构和查找算法,属于个人整理。初学编程可以用这里的东西联系一下,看一看也挺有意思博主个人不认为js中算法数据结构不重要,毕竟这是程序开发的基本功。本文还会根据博主学习进展和时间安排不定期更新 数据结构部分 列...
摘要:手势解锁界面一些对安全要求比较高的少不了锁屏页面,而手势解锁对于用户来说使用方便,对于程序员来说小有挑战,怎么有弃之不用的道理。 ionic 2+ 手势解锁界面 一些对安全要求比较高的app少不了锁屏页面,而手势解锁对于用户来说使用方便,对于程序员来说小有挑战,怎么有弃之不用的道理。 效果图 效果图处理短,方便大家阅读showImg(https://segmentfault.co...
阅读 3772·2021-11-11 11:02
阅读 3503·2021-10-11 10:57
阅读 3611·2021-09-22 16:00
阅读 1850·2021-09-02 15:15
阅读 1336·2019-08-30 15:56
阅读 1012·2019-08-30 15:54
阅读 2736·2019-08-30 12:43
阅读 3544·2019-08-29 16:06
极致性价比!云服务器续费无忧!
Tesla A100/A800、Tesla V100S等多种GPU云主机特惠2折起,不限台数,续费同价。
NVIDIA RTX 40系,高性价比推理显卡,满足AI应用场景需要。
乌兰察布+上海青浦,满足东推西训AI场景需要