前言

我曾以为func()其实就是window.func()

function func(){
console.log('this : ' + this);
} func();//this : [object Window]
window.func();//this : [object Window]

直到

'use strict'
function func(){
console.log('this : ' + this);
}
func();//this : undefined
window.func();//this : [object Window]

也曾为输出inside this : [object Window] 而困惑不已

function outside(){
console.log('outside this : ' + this);//outside this : [object Object]
function inside(){
console.log('inside this : ' + this);//inside this : [object Window]
}
inside();
}
let obj = {
outside : outside
}
obj.outside();

曾感慨Java之美好[1],唾弃JavaScript中this的‘灵活’。

...

一直到我尝试总结出this的规律:

1.构造函数中的this关键字在任何模式下都指向new出来的对象;

2.严格模式下this关键字指向调用该函数的对象,如果该函数未被对象调用,则 this === 'undefined';

3.非严格模式下this关键字指向调用该函数的对象,如果该函数未被对象调用 this === window;

再到后来,拜读了JavaScript语言精粹,知晓了4种调用模式,知晓了箭头函数的一个我不曾知晓的作用,结合过往,我感觉自己已经摸清了this的规律,亦或者至少摸清了一部分的规律,特撰此文,作为总结;

首先,此文所有代码运行环境皆为浏览器,所以我不会强调global;

再者,function中this指向被确定于function被调用时(抛开箭头函数和class不论),

类似像下面这种代码,我觉得没有提的必要;

function func(){
console.log('this : ' + this);
}
func;
setTimeout(func,100);
//我想表达的意思是 func是被setTimeout调用的 同样我也可以写一个mySetTimeout
mySetTimeout = function(func,delay){
setTimeout(func.bind({}),delay); //我们应该把目光放在function调用时
}
mySetTimeout(func,100);
//在不运行代码的前提下,如果不看mySetTimeout代码,能准确判断this是什么吗?

最后,我不想提with,因为with的使用往往会引起歧义,就如同下面的代码,明明调用时的代码一模一样,但一个在全局作用域window中调用func,而另一个在obj的作用域中调用,输出的结果天差地别。

function func(){
console.log('this : ' + this);
}
let obj = {};
with(obj){
func();//this : [object Window]
}
obj.func = func;
with(obj){
func();//this : [object Object]
}

接下来的内容我将以下图中的思路展开:

ES6之前

这里的主要思路还是沿用的JavaScript语言精粹。

函数调用模式

JavaScript中的function不同于Java,Java虽然说万物皆对象,但是基础类型和function就不是对象。Java中的function只是对象的行为,但是JavaScript不同,JavaScript虽然同时包含了一些像原型、函数柯里化等编程思想,但是在万物皆对象这一方面,反而比Java更像是面向对象编程。JavaScript中的function是支持直接调用的。

在非严格模式:

function func(){
console.log('this : ' + this);
}
func();//this : [object Window]

在严格模式:

'use strict'
function func(){
console.log('this : ' + this);
}
func();//this : undefined

方法调用模式

方法调用模式就是把function当成对象的行为来调用,既然是对象的行为,那么function中的this指向的当然是这个调用的对象了。

在非严格模式:

let _self = null;
function func(){
_self = this;
}
let obj = {
func : func
}
obj.func();
console.log('_self === obj : ' + (_self === obj));//_self === obj : true

在严格模式:

'use strict'
let _self = null;
function func(){
_self = this;
}
let obj = {
func : func
}
obj.func();
console.log('_self === obj : ' + (_self === obj));//_self === obj : true

构造调用模式

构造调用模式就是把function当做构造函数调用,在其左边加上new关键字,为了迎合代码规范,这里的function我将以大写字母开头。

在非严格模式:

let _self = null;
function Person(){
_self = this;
}
let person = new Person();
console.log('_self === person : ' + (_self === person));//_self === person : true

在严格模式:

'use strict'
let _self = null;
function Person(){
_self = this;
}
let person = new Person();
console.log('_self === person : ' + (_self === person));//_self === person : true

构造函数这里我觉得有必要扩展一下:

1.构造函数中返回对象(非基础类型),会影响上面的结果;

let _self = null;
function Person(){
_self = this;
return window;
}
let person = new Person();
console.log('_self === person : ' + (_self === person));//_self === person : false
console.log('window === person : ' + (window === person));//window === person : true

2.省略new关键字,同样会影响上面的结果;

let _self = null;
function Person(){
_self = this;
}
let person = Person();
console.log('_self === person : ' + (_self === person));//_self === person : false
console.log('window === person : ' + (window === person));//window === person : false
console.log('typeof person : ' + typeof person);//typeof person : undefined

在Person调用时省略new关键字还可能会污染全局作用域:

function Person(){
this.personName = 'person';
}
let person = Person();
console.log('person.personName : '+person.personName);//Cannot read property 'personName' of undefined
console.log('window.personName : '+window.personName);//window.personName : person

蠢办法解决调用构造函数不用new关键字的:

function Person(){
if(this === window){
throw Error('You must use the new keyword.');
}
this.personName = 'person';
}
let person = Person();//You must use the new keyword.

改进版

function Person(){
let context;
(function(){
context = this;
}())
if(this === context){
throw Error('You must use the new keyword.');
}
this.personName = 'person';
}
let person = Person();//You must use the new keyword.

特指调用模式

bind虽然是es6的,但是我也放到这个模式一起讲了,因为我觉得把bind和apply、call一起讲可能会更容易理解一些。

apply

apply的第一个参数是绑定的对象,第二个参数是array。call和apply的不同之处在于call的第二个参数对于function中arguments的第一位,第三个参数对于function中的arguments的第二位,以此类推;而apply的第二个参数对应function中的arguments。由于这里主要是讲this,所以第二个参数的例子就不提了,后面的call也一样。

在非严格模式:

function func(){
console.log('this : ' + this);
}
func.apply({});//this : [object Object]
func.apply(window);//this : [object Window]
func.apply(null);//this : [object Window]
func.apply();//this : [object Window]

在严格模式:

'use strict'
function func(){
console.log('this : ' + this);
}
func.apply({});//this : [object Object]
func.apply(window);//this : [object Window]
func.apply(null);//this : null
func.apply();//this : undefined

实现apply

满足条件

1.把第一个参数绑定到调用myApply的function运行时的this;

2.第二个参数应与调用myApply的function的arguments内容一致;

3.严格模式和非严格模式第一个参数为null或undefined时情况要与apply函数一致;

代码
Function.prototype.myApply = function(){
var context,arr;
//谁调用的myApply this就指向谁
if(typeof this !== 'function'){
throw Error('typeof this !== "function"');
}
context = arguments[0];
arr = arguments[1] ? arguments[1] : [];
if(typeof context === 'undefined' || context === null){
//满足条件3
context = (function(){
return this;
}());
}
if(typeof context === 'undefined'){
this(...arr);
}else{
context.f = this;
context.f(...arr);
}
}

call

call如果只传入第一个参数,结果和只传入第一个参数的apply是一致的。

在非严格模式:

function func(){
console.log('this : ' + this);
}
func.call({});//this : [object Object]
func.call(window);//this : [object Window]
func.call(null);//this : [object Window]
func.call();//this : [object Window]

在严格模式:

'use strict'
function func(){
console.log('this : ' + this);
}
func.call({});//this : [object Object]
func.call(window);//this : [object Window]
func.call(null);//this : null
func.call();//this : undefined

实现call

满足条件

1.把第一个参数绑定到调用myCall的function运行时的this;

2.除第一个参数外其余参数组成的数组应与调用myCall的function的arguments内容一致;

3.严格模式和非严格模式第一个参数为null或undefined时情况要与call函数一致;

代码
Function.prototype.myCall = function(){
var context,arr;
//谁调用的myCall this就指向谁
if(typeof this !== 'function'){
throw Error('typeof this !== "function"');
}
context = arguments[0];
//差异点 call与apply的传值方式所致
arr = [...arguments].slice(1);//手动转型
if(typeof context === 'undefined' || context === null){
//满足条件3
context = (function(){
return this;
}());
}
if(typeof context === 'undefined'){
this(...arr);
}else{
context.f = this;
context.f(...arr);
}
}

bind

bind和call很相似,主要的不同点在于func.call(window)立马就调用了,而func.bind(window)会返回一个绑定了window的function,但是这个function还没有执行。可以这样理解func.bind(window)()的效果与func.call(window)一致。

在非严格模式:

function func(){
console.log('this : ' + this);
} func.bind({})();//this : [object Object]
func.bind(null)();//this : [object Window]
func.bind()();//this : [object Window]
let obj = {
func : func.bind(window)
}
obj.func();//this : [object Window]
//构造函数
let _self = null;
function Person(){
_self = this;
}
let P = Person.bind(window);
let person = new P();
console.log('_self === person : ' + (_self === person));//_self === person : true
console.log('window === person : ' + (window === person));//window === person : false

在严格模式:

'use strict'
function func(){
console.log('this : ' + this);
} func.bind({})();//this : [object Object]
func.bind(null)();//this : null
func.bind()();//this : undefined
let obj = {
func : func.bind(window)
}
obj.func();//this : [object Window]
//构造函数
let _self = null;
function Person(){
_self = this;
}
let P = Person.bind(window);
let person = new P();
console.log('_self === person : ' + (_self === person));//_self === person : true
console.log('window === person : ' + (window === person));//window === person : false

从上面的例子,我们不单单可以发现bind在严格模式和非严格模式下的不同,还可以得出构造调用模式的优先级最高,bind其次,方法调用模式和函数调用模式最低。

实现bind

满足条件

1.把第一个参数绑定到调用myBind的function运行时的this;

2.将除第一个参数外其余参数与function中参数合并;

3.严格模式和非严格模式第一个参数为null或undefined时情况要与bind函数一致;

代码
Function.prototype.myBind = function(){
var context,arr,_self;
//谁调用的myBind this就指向谁
if(typeof this !== 'function'){
throw Error('typeof this !== "function"');
}
context = arguments[0];
arr = [...arguments].slice(1);//手动转型
if(typeof context === 'undefined' || context === null){
//满足条件3
context = (function(){
return this;
}());
}
_self = this;
return function(){
if(typeof context === 'undefined'){//严格模式
_self(arr.concat(...arguments));
}else{
context.f = _self;
context.f(arr.concat(...arguments));
} }
}

ES6

据我所知,有一部分人,他们奉行箭头函数+class来解决一切问题。我对此观点的正确性不表态,但是这样做能减少很多判断this的麻烦。

箭头函数

箭头函数没有this,箭头函数中的this来自于它处于的作用域链中的上一层。我在前言中说过,我曾为输出inside this : [object Window] 而困惑不已,但是我现在把代码略微修改一下,输出就将符合我的预期(inside继承了outside的this值)。

function outside(){
console.log('outside this : ' + this);//outside this : [object Object]
let inside = ()=>{
console.log('inside this : ' + this);//inside this : [object Object]
}
inside();
}
let obj = {
outside : outside
}
obj.outside();

要是把outside也改成箭头函数,结果又会大不一样

let outside = ()=>{
console.log('outside this : ' + this);//outside this : [object Window]
let inside = ()=>{
console.log('inside this : ' + this);//inside this : [object Window]
}
inside();
}
let obj = {
outside : outside
}
obj.outside();

因为箭头函数的this值是继承于它身处的作用域上一层的this,outside上一层是全局作用域,不会再发生更改了,所以这里就算用方法调用模式,也无法改变this的值。

在非严格模式:

let func = ()=>{
console.log('this : ' + this);
}
func();//this : [object Window]
let obj = {
func : func
}
obj.func();//this : [object Window]
func.apply({});//this : [object Window]
func.call({});//this : [object Window]
func.bind({})();//this : [object Window]
func.apply(null);//this : [object Window]
func.apply();//this : [object Window] let _self = null;
let Person = ()=>{
_self = this;
}
let person = new Person();//Person is not a constructor

在严格模式:

'use strict'
let func = ()=>{
console.log('this : ' + this);
}
func();//this : [object Window]
let obj = {
func : func
}
obj.func();//this : [object Window]
func.apply({});//this : [object Window]
func.call({});//this : [object Window]
func.bind({})();//this : [object Window]
func.apply(null);//this : [object Window]
func.apply();//this : [object Window] let _self = null;
let Person = ()=>{
_self = this;
}
let person = new Person();//Person is not a constructor

观察上述代码运行结果可知:

1.严格模式和非严格模式对箭头函数中的this无影响;

2.箭头函数无法当作构造函数使用;

3.箭头函数中的this只与自身处于的作用域链上一层有关;

class

第一次看到class的用法时,我就不禁感慨原型的强大,对于我这种以前使用Java的人来说,class真的是太友好了。

在非严格模式:

let constructorThis = null;
let funcThis = null;
let staticFuncThis = null;
class Person{
constructor(){
constructorThis = this;
}
func(){
funcThis = this;
}
static staticFunc(){
staticFuncThis = this;
}
} let person = new Person();
person.func();
Person.staticFunc(); console.log('constructorThis === person : ' + (constructorThis === person));//constructorThis === person : true
console.log('funcThis === person : ' + (funcThis === person));//funcThis === person : true
console.log('staticFuncThis === person : ' + (staticFuncThis === person));//staticFuncThis === person : false
console.log('staticFuncThis : ' + staticFuncThis);//staticFuncThis : class Person...

在严格模式:

'use strict'
let constructorThis = null;
let funcThis = null;
let staticFuncThis = null;
class Person{
constructor(){
constructorThis = this;
}
func(){
funcThis = this;
}
static staticFunc(){
staticFuncThis = this;
}
} let person = new Person();
person.func();
Person.staticFunc(); console.log('constructorThis === person : ' + (constructorThis === person));//constructorThis === person : true
console.log('funcThis === person : ' + (funcThis === person));//funcThis === person : true
console.log('staticFuncThis === person : ' + (staticFuncThis === person));//staticFuncThis === person : false
console.log('staticFuncThis : ' + staticFuncThis);//staticFuncThis : class Person...

观察上述代码运行结果可知:

1.严格模式和非严格模式对class function中的this无影响;

2.构造函数和普通方法的this就是new出来的值(和方法调用模式、构造调用模式一致)

3.静态方法的this就是这个class(还是和方法调用模式一致 毕竟是用class调用的静态方法)

结尾

由于本人水平有限,如有缺失和错误,还望告知。


  1. Java中function只能是方法,被对象或者类调用。非静态方法被对象调用时,this是这个调用的对象;静态方法被类调用时,则没有this;

JavaScript——深入了解this的更多相关文章

  1. JavaScript之父Brendan Eich,Clojure 创建者Rich Hickey,Python创建者Van Rossum等编程大牛对程序员的职业建议

    软件开发是现时很火的职业.据美国劳动局发布的一项统计数据显示,从2014年至2024年,美国就业市场对开发人员的需求量将增长17%,而这个增长率比起所有职业的平均需求量高出了7%.很多人年轻人会选择编 ...

  2. javascript中的Array对象 —— 数组的合并、转换、迭代、排序、堆栈

    Array 是javascript中经常用到的数据类型.javascript 的数组其他语言中数组的最大的区别是其每个数组项都可以保存任何类型的数据.本文主要讨论javascript中数组的声明.转换 ...

  3. Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收

    执行环境有全局执行环境和函数执行环境之分,每次进入一个新执行环境,都会创建一个搜索变量和函数的作用域链.函数的局部环境不仅有权访问函数作用于中的变量,而且可以访问其外部环境,直到全局环境.全局执行环境 ...

  4. 探究javascript对象和数组的异同,及函数变量缓存技巧

    javascript中最经典也最受非议的一句话就是:javascript中一切皆是对象.这篇重点要提到的,就是任何jser都不陌生的Object和Array. 有段时间曾经很诧异,到底两种数据类型用来 ...

  5. 读书笔记:JavaScript DOM 编程艺术(第二版)

    读完还是能学到很多的基础知识,这里记录下,方便回顾与及时查阅. 内容也有自己的一些补充. JavaScript DOM 编程艺术(第二版) 1.JavaScript简史 JavaScript由Nets ...

  6. 《Web 前端面试指南》1、JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

  7. JavaScript权威指南 - 函数

    函数本身就是一段JavaScript代码,定义一次但可能被调用任意次.如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法.用于初始化一个新创建的对象的函数被称作构造函数. 相对 ...

  8. JavaScript自定义浏览器滚动条兼容IE、 火狐和chrome

    今天为大家分享一下我自己制作的浏览器滚动条,我们知道用css来自定义滚动条也是挺好的方式,css虽然能够改变chrome浏览器的滚动条样式可以自定义,css也能够改变IE浏览器滚动条的颜色.但是css ...

  9. JavaScript进阶之路(一)初学者的开始

    一:写在前面的问题和话 一个javascript初学者的进阶之路! 背景:3年后端(ASP.NET)工作经验,javascript水平一般般,前端水平一般般.学习资料:犀牛书. 如有误导,或者错误的地 ...

  10. 梅须逊雪三分白,雪却输梅一段香——CSS动画与JavaScript动画

    CSS动画并不是绝对比JavaScript动画性能更优越,开源动画库Velocity.js等就展现了强劲的性能. 一.两者的主要区别 先开门见山的说说两者之间的区别. 1)CSS动画: 基于CSS的动 ...

随机推荐

  1. ASP.NET Core 3.1使用JWT认证Token授权 以及刷新Token

    传统Session所暴露的问题 Session: 用户每次在计算机身份认证之后,在服务器内存中会存放一个session,在客户端会保存一个cookie,以便在下次用户请求时进行身份核验.但是这样就暴露 ...

  2. 5.innodb B+tree索引

    索引基础 索引是数据结构 1.图例 2.B+tree 特征 1.非叶子节点不保存数据,只用来索引,数据都保存在叶子节点 2.查询任何一条数据,查询的索引深度都是一样的 3. B+ 树中各个页之间是通过 ...

  3. CSS系列 (03):CSS三大特性

    层叠性 层叠性指的是样式的优先级,当产生冲突时以优先级高的为准,优先级相同时取后面定义的属性样式. 继承性 继承性指的是子孙元素可以继承父元素的属性. 记录一下开发中常用的继承属性: 字体系列 fon ...

  4. 阿里巴巴java开发手册-泰山版 下载

    最近,阿里的<Java开发手册>又更新了,这个版本历经一年的修炼,取名:<Java开发手册(泰山版)>正式出道.正所谓无规矩不成方圆,在程序员的世界里,也存在很多规范,阿里出版 ...

  5. [leetcode]692. Top K Frequent Words频率最高的前K个单词

    这个题的排序是用的PriorityQueue实现自动排列,优先队列用的是堆排序,堆排序请看:http://www.cnblogs.com/stAr-1/p/7569706.html 自定义了优先队列的 ...

  6. 伯俊BOS2.0店铺收入对账功能设计

    一.客户需求 通过导入银行POS机流水,将流水与ERP系统的零售付款数据进行对比,统计差异! 二.功能设计 1.新增"POS机号对应表单",用于维护POS机与erp店仓对应 2.新 ...

  7. 第十一章节 BJROBOT PS3 手柄控制【ROS全开源阿克曼转向智能网联无人驾驶车】

    1.把小车架空平放在地板上.   2.用 USB 线将 PS3 蓝牙手柄连接至小车主控端,初次连接手柄上的 4 个红色指示灯会同时闪烁; 3.按下手柄中间的圆形配对键,然后等待红灯闪烁至停止. 4.此 ...

  8. 整合.NET WebAPI和 Vuejs——在.NET单体应用中使用 Vuejs 和 ElementUI

    .NET简介 .NET 是一种用于构建多种应用的免费开源开发平台,例如: Web 应用.Web API 和微服务 云中的无服务器函数 云原生应用 移动应用 桌面应用 1). Windows WPF 2 ...

  9. Spark学习进度7-综合案例

    综合案例 文件排序 解法: 1.读取数据 2.数据清洗,变换数据格式 3.从新分区成一个分区 4.按照key排序,返还带有位次的元组 5.输出 @Test def filesort(): Unit = ...

  10. 基于nginx实现web服务器的双机热备

    1.适用场景 对于部署重要的服务,会使用两台服务器,互相备份,共同执行同一服务.当一台服务器出现故障时,可以由另一台服务器承担服务任务,从而在不需要人工干预的情况下,自动保证系统能持续提供服务.双机热 ...