深入Javascript之this
前言
近期准备好好的读一读《你不知道的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操作符干了什么?
创建(或者说构造)一个全新的对象。
这个新对象会被执行[[原型]]连接。
这个新对象会绑定到函数调用的this。
如果函数没有返回其他对象,那么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的绑定对象。
- 由new调用?绑定到新创建的对象。
- 由call或者apply(或者bind)调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到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的更多相关文章
- JavaScript之父Brendan Eich,Clojure 创建者Rich Hickey,Python创建者Van Rossum等编程大牛对程序员的职业建议
软件开发是现时很火的职业.据美国劳动局发布的一项统计数据显示,从2014年至2024年,美国就业市场对开发人员的需求量将增长17%,而这个增长率比起所有职业的平均需求量高出了7%.很多人年轻人会选择编 ...
- javascript中的Array对象 —— 数组的合并、转换、迭代、排序、堆栈
Array 是javascript中经常用到的数据类型.javascript 的数组其他语言中数组的最大的区别是其每个数组项都可以保存任何类型的数据.本文主要讨论javascript中数组的声明.转换 ...
- Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收
执行环境有全局执行环境和函数执行环境之分,每次进入一个新执行环境,都会创建一个搜索变量和函数的作用域链.函数的局部环境不仅有权访问函数作用于中的变量,而且可以访问其外部环境,直到全局环境.全局执行环境 ...
- 探究javascript对象和数组的异同,及函数变量缓存技巧
javascript中最经典也最受非议的一句话就是:javascript中一切皆是对象.这篇重点要提到的,就是任何jser都不陌生的Object和Array. 有段时间曾经很诧异,到底两种数据类型用来 ...
- 读书笔记:JavaScript DOM 编程艺术(第二版)
读完还是能学到很多的基础知识,这里记录下,方便回顾与及时查阅. 内容也有自己的一些补充. JavaScript DOM 编程艺术(第二版) 1.JavaScript简史 JavaScript由Nets ...
- 《Web 前端面试指南》1、JavaScript 闭包深入浅出
闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...
- JavaScript权威指南 - 函数
函数本身就是一段JavaScript代码,定义一次但可能被调用任意次.如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法.用于初始化一个新创建的对象的函数被称作构造函数. 相对 ...
- JavaScript自定义浏览器滚动条兼容IE、 火狐和chrome
今天为大家分享一下我自己制作的浏览器滚动条,我们知道用css来自定义滚动条也是挺好的方式,css虽然能够改变chrome浏览器的滚动条样式可以自定义,css也能够改变IE浏览器滚动条的颜色.但是css ...
- JavaScript进阶之路(一)初学者的开始
一:写在前面的问题和话 一个javascript初学者的进阶之路! 背景:3年后端(ASP.NET)工作经验,javascript水平一般般,前端水平一般般.学习资料:犀牛书. 如有误导,或者错误的地 ...
- 梅须逊雪三分白,雪却输梅一段香——CSS动画与JavaScript动画
CSS动画并不是绝对比JavaScript动画性能更优越,开源动画库Velocity.js等就展现了强劲的性能. 一.两者的主要区别 先开门见山的说说两者之间的区别. 1)CSS动画: 基于CSS的动 ...
随机推荐
- 201521123044 《Java程序设计》第4周学习总结
1. 本章学习总结 2. 书面作业 1. 注释的应用 使用类的注释与方法的注释为前面编写的类与方法进行注释,并在Eclipse中查看.(截图) 答: 2. 面向对象设计(大作业1,非常重要) 2.1 ...
- 201521123037 《Java程序设计》第1周学习总结
#1.本章学习总结 了解java的发展历程 能够区分JVM.JRE.JDK之间的关系 了解文件名.类名要相同,且文件名大小写代表不同的名称 初步了解java编程语句 学会用控制台编译java程序 #2 ...
- 201521123045 《JAVA程序设计》 第14周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...
- PTA分享码-Java
主要用于Java语法练习,非竞赛类题目. 1. Java入门 959dbf0b7729daa61d379ec95fb8ddb0 2. Java基本语法 23bd8870e ...
- 【java】聊聊java里的接口
接口的概念 java中的接口用于描述类应该具备什么样的功能,而不给出具体的实现,一个类可以“实现”多个接口 [注意]接口不是类,而是对类的一组描述 还是让我们通过一个例子来看看接口如何运作吧! ...
- Hibernate的Configuration对象的configure()方法
Configuration configuration=new Configuration(); configuration.configure(); 在Hibernate底层实现configure( ...
- MyBatis学习(一)简介及入门案例
1.什么是MyBatis? MyBatis是一个支持普通SQL查询,存储过程,和高级映射的优秀持久层框架.MyBatis去掉了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBati ...
- JavaScript中的位置屬性
屏幕中的位置(直接使用,無需前綴): screenLeft.screenTop:除了火狐都支持 screenX.screenY: 窗口的大小(谷歌的inner=outer,直接使用,無需前綴): in ...
- ng-model值字符串转数值型(convertToNumber directive)
<select ng-model="model.id" convert-to-number> <option value="0">Zer ...
- WebApi实现原理解析笔记
这是我看过WebApi实现代码后的一些总结,一方面加深自己的记忆,另外也希望能够帮助大家更深入的了解WebApi. 注:暂时没有好好的整理,可能有些晦涩难懂. Webapi 控制器类必须实现IHttp ...