前言

近期准备好好的读一读《你不知道的JavaScript(上卷)》这本书,俗话说的好,好记性不如烂笔头,读到this这章感觉是时候需要一些笔记了。文中如有错误之处,欢迎指出。

什么是this?

什么是this,我们先来看看作者的回答。

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。

this的4种绑定方式

默认绑定

function foo() {
console.log( this.a );
} var a = 2; foo(); // 2

这段代码输出2 , 说明this默认绑定到了全局对象

隐式绑定

function foo() {
console.log( this.a );
} var obj = {
a: 2,
foo: foo
}; obj.foo(); // 2

这段代码this绑定到了obj对象,当函数引用有上下文对象(context)时,隐式绑定规则会把this绑定到这个上下文对象。

隐式丢失问题

// 例子1111
function foo() {
console.log( this.a );
} var obj = {
a: 2,
foo: foo
}; var bar = obj.foo; // 函数别名!

var a = "oops, global"; // a是全局对象的属性” bar(); // "oops, global" //2222 function foo() {
console.log( this.a );
} var obj = {
a: 2,
foo: foo
}; var a = "oops, global"; // a是全局对象的属性 setTimeout( obj.foo, 100 ); // "oops, global

例子1111虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。例子222在setTimeout函数中丢失了this绑定,道理是一样的,我们可以把参数传递看成一种隐式赋值。

显式绑定

在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上。

那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?

学过js的估计对 call,apply和bind都不陌生,js提供的这些原生方法就给我们提供了显式绑定this的途径。


function foo() {
console.log( this.a );
} var obj = {
a:2
}; foo.call( obj ); // 2

通过foo.call(..),我们可以在调用foo时强制把它的this绑定到obj上。

如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(..)、new Boolean(..)或者new Number(..))。这通常被称为“装箱”。

“从this绑定的角度来说,call(..)和apply(..)是一样的,它们的区别体现在其他的参数上,但是现在我们不用考虑这些。”

硬绑定-bind的实现

我们已经知道了this绑定的4种方式,但是使用call和apply我们还是不能解决隐式丢失的问题,思考以下例子:

function foo() {
console.log( this.a );
} var obj = {
a:2
}; var bar = function() {
foo.call( obj );
}; bar(); // 2
setTimeout( bar, 100 ); // 2 bar.call(windows) //无法再修改this

通过call我们在函数bar内强制指定了foo的this,之后不论如何调用bar,它总会在obj上手动调用foo,这种绑定是一种显示强制绑定,因此称为硬绑定。这就是bind的产生来由(因为硬绑定比较常用,es5中新增了bind方法),通过硬绑定制定this,上面代码现在就变成了

...略
var bar = foo.bind(obj)
bar(); // 2
setTimeout( bar, 100 ); // 2

new绑定

关于new,我们首先梳理一下基础知识。

new操作符干了什么?

  1. 创建(或者说构造)一个全新的对象。

  2. 这个新对象会被执行[[原型]]连接。

  3. 这个新对象会绑定到函数调用的this。

  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象”

function foo(a) {
this.a = a
} var bar = foo(2)
var baz = new foo(2) bar.a //Cannot read property 'a' of undefined baz.a // 2 this绑定到了foo

绑定规则优先级和es6的this新特性

如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this的绑定对象。

  1. 由new调用?绑定到新创建的对象。
  2. 由call或者apply(或者bind)调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到undefined,否则绑定到全局对象。

ES6中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)。

几个小例子看懂this

function showThis () {
console.log(this)
}
function showStrictThis () {
'use strict'
console.log(this)
}
showThis() // window
showStrictThis() // undefined

优先级最小的this对象,默认绑定。

思考下面的例子:

var person = {
name: '11',
showThis () {
return this
}
} person.showThis() === person //true var person2 = {
name: '22',
showThis () {
return person.showThis()
}
} var person3 = {
name: '33',
showThis () {
var retrunThis = person.showThis
return retrunThis()
}
} person.showThis() //person
person2.showThis() //?
person3.showThis() //?

我们首先要找到调用位置,在2里是这句return person.showThis(),隐式绑定了一个person对象,所以输出person ,3 是return retrunThis() ,this默认绑定到全局,返回window.

function showThis () {
return this
}
var person = { name: 'person' }
showThis() // window
showThis.call(p1) // person
showThis.apply(p1) // person

通过显式绑定指定了context object。

function showThis () {
return this
}
var person = { name: 'person' } var personBind = showThis.bind(person) personBind() //person var person2 = { name: 'person2' } personBind.call(person2) //person

bind方法强绑定了this,已经无法再通过显式绑定切换this。

function showThis () {
return this
}
var person = { name: 'person' }
var person2 = { name: 'person2' } var personBind = showThis.bind(person) personBind() //person
new personBind() //showThis

new优先级高于bind,所以可以覆盖this。


function foo() {
setTimeout(() => {
// 这里的this在词法上继承自foo()
console.log( this.a );
},100);
} var obj = {
a:2
}; foo.call( obj ); // 2

箭头函数并不是使用function关键字定义的,而是使用被称为“胖箭头”的操作符=>定义的。箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。箭头函数的绑定无法被修改,包括new。 关于箭头函数网上有很多详细全面的讲解。这里不再展开。

后记

原型链,闭包,堆栈结构等等。。。javascript入门我们都觉得是比较简单的,找一份敲代码的工作真不难,只要努力搬砖就好了。但是如果不去深入了解js的底层机制,这条道路恐怕是走不远的。没有好的基础,我们可以学会使用react,使用vue,使用别人的插件,但是自己造轮子还是远远达不到的。

前端水很深,需要学的的东西太多太杂,真要学,几乎都能学都有用,都不学,照样敲代码没什么大问题,工作业务积累应付工作还是足够的,不够?那就加个班呗。加油吧~为了不被淘汰,共勉~

如果觉得本文对你有所帮助,就star一下吧~大传送之术! 我的博客Github

深入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. 201521123055 《Java程序设计》第13周学习总结

    1. 本章学习总结 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu.edu.cn,分析返回结果有何不同?为什么会有这样的不同? 1.2 te ...

  2. 201521123114 《Java程序设计》第13周学习总结

    1.本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu. ...

  3. 小甲鱼:Python学习笔记001_变量_分支_数据类型_运算符等基础

    1.下载并安装Python 3.5.0 Python是一个跨平台语言,Python从3.0的版本的语法很多不兼容2版本,官网找到最新的版本并下载:http://www.python.org 2.IDL ...

  4. ops-web运维平台data.jsp-jquery-mootools

    data.jsp页面, 下面列出的是 <body>部分 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang=& ...

  5. 【译】The Accidental DBA:Troubleshooting

    最近重新翻看The Accidental DBA,将Troubleshooting部分稍作整理,方便以后查阅.此篇是Part 3Part 1:The Accidental DBA:SQL Server ...

  6. stsuts2的一些问题

    1.什么是struts2? struts2是一个基于MVC设计模式的框架, 2.struts2的工作原理. 1.客户端发送一个请求 2.经过核心过滤器StrutsPrepareAndExecuteFi ...

  7. Apache POI

    Apache POI 用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能.POI为"Po ...

  8. Linux 常用命令之二

    整理以前学习Linux的笔记. 查找目录.查看当前所在路径.新建文件.查看文件内容.修改文件内容.压缩文件操作.搜索命令.管道命令.查看进程.终止进程.查看端口. 7,命令find--查找目录 fin ...

  9. Python里Pure paths、PurePosixPath、PureWindowsPath的区别

    Python是跨平台的,可以在不同的操作系统上运行.但是不同系统上路径 的表示方式是不一样的. 例如windows上路径使用“\”分割子目录和父目录,linux上是使用“/”来分割.这就是PurePo ...

  10. Oculus关于Internal Error:OVR53225466报错解决方法

    安装Oculus过程中可能会出现Internal Error:OVR53225466报错提示,如附件所示: 解决方法:修改hosts文件 操作方法: (1)以管理员方式打开记事本: (2)打开C:\W ...