你不知道的JavaScript——第二章:this全面解析
1调用位置
调用栈:为了到达当前执行位置所调用的所有函数。
function baz(){
//当前调用栈:baz
//因此,当前调用位置是全局作用域
console.log('baz');
bar(); //bar的调用位置
} function bar(){
//当前调用栈:baz->bar
//因此,当前调用位置在baz
console.log('bar');
foo(); //foo的调用位置
}
function foo(){
//当前调用栈:baz->bar->foo
//因此,当前调用位置在bar中
console.log('foo');
}
baz(); //baz的调用位置
2绑定规则:
2.1默认绑定:
function foo(){
console.log(this.a);
}
var a=2;
foo();//
函数调用时应用了this的默认绑定,因此this指定全局对象。
如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined。
function foo(){ //严格模式下声明
"use strict";
console.log(this.a);
}
var a=2;
foo();//TypeError:this is undefined
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
虽然this的绑定规则完全取决于调用的位置,但是只有foo()运行在非strict mode下时,默认绑定才能绑定到全局对象。
在严格模式下调用foo()则不影响默认绑定。!!!!
function foo(){
console.log(this.a);
}
var a=2;
(function(){ //严格模式下调用
"use strict";
foo();//
})();
2.2隐式绑定
需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,!!不过这种说法可能会造成一些误导。
funtion foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo
}
obj.foo();//
无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。
然而,调用位置会使用obj上下文来引用函数,因此可以说函数被调用时obj对象“拥有”或者“包含”函数引用。
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
!!!!!!!!!!!!!!!!
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。(函数直接关联层起作用)
function foo(){
console.log(this.a);
}
var obj2={
a:42,
foo:foo
}
var obj1={
a:2,
obj2:obj2
}
obj1.obj2.foo();//
隐式丢失:
被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否在严格模式。
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo
}
var bar=obj.foo;
var a='oops,global';
bar();//'oops,global'
虽然bar是obj.foo的一个引用,但实际上,它引用的是foo函数本身,因此此时bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
function foo(){
console.log(this.a);
}
function doFoo(fn){
fn();
}
var obj={
a:2,
foo:foo
}
var a='oops,global';
doFoo(obj.foo);//'oops,global';
参数传递其实就是一种隐式赋值。
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo
}
var a="oops,global";
setTimeout(obj.foo,100);//"oops, global";
JavaScript环境中内置的setTimeout()函数实现和下面的伪代码类似:
function setTimeout(fn,delay){
//等待delay毫秒
fn();//调用位置
}
2.3显式绑定 (call,apply,bind)
function foo(){
console.log(this.a);
}
var obj={
a:2
}
foo.call(obj);//
通过foo.call(...),我们可以在调用foo时强制把它的this绑定到obj上。
如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(…)、new Boolean(…)或者new Number(…))。这通常被称为“装箱”。
1.硬绑定: 显式的强制绑定
function foo(){
console.log(this.a);
}
var obj={
a:2
};
var bar=function(){
foo.call(obj);
};
bar(); //
setTimeout(bar,100); //
//硬绑定的bar不可能再修改它的this
bar.call(windwo);//2
硬绑定的典型应用场景就是创建一个包裹函数,负责接收参数并返回值。
function foo(something){
console.log(this.a,something);
return this.a+something;
}
var obj={
a:2
};
var bar=function(){
return foo.apply(obj,arguments);
};
var b=bar(3); //2 3
console.log(b);//
另一种使用方法是创建一个可以重复使用的辅助函数:
function foo(something){
console.log(this.a,something);
return this.a+something;
}
//简单的辅助绑定函数
function bind(fn,obj){
return function(){
fn.appl(obj,arguments);
};
}
var obj={
a:2
};
var bar=bind(foo,obj);
var b=bar(3);//2 3
console.log(b);//
ES5提供了内置的方法Function.prototype.bind
function foo(something){
console.log(this.a, something);
return this.a+something;
}
var obj={
a:2
};
var bar=foo.bind(obj);
var b=bar(3); //2 3
console.log(b); //
bind(…)会返回一个硬编码的新函数,它会把你指定的参数设置为this的上下文并调用原始函数。
2.API调用的“上下文”
function foo(el){
console.log(el, this.id);
}
var obj={
id:"awesome"
};
//调用foo(…)时把this绑定到obj
[1,2,3].forEach(foo,obj);//1 awesome 2 awesome 3 awesome
2.4new绑定
包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。注:实际上并不存在所谓的“”构造函数“”,只有对于函数的“”构造调用“”。
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1.创建(或者说构造)一个全新的对象。
2.这个新对象会被执行 [[Prototype]]连接
3.这个新对象会绑定到函数调用的this。
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a){
this.a=a;
}
var bar=new foo(2);//函数没有返回其他对象,new表达式中的函数调用会自动返回新对象。
console.log(bar.a)//
使用new来调用foo(…)时,我们会构造一个新对象并把它绑定到foo(…)调用中的this上。
3优先级:
判断this:
1.函数是否在new调用(new绑定)?如果是的话this绑定的是新创建的对象。 var bar=new foo();
2.函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。 var bar=foo.call(obj2);
3.函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。 var bar=obj1.foo();
4.如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。 varbar=foo();
隐式绑定和显示绑定比较显示绑定的优先级更高。例:
function foo(){
console.log(this.a);
}
var obj1={
a:2,
foo:foo
}
var obj2={
a:3,
foo:foo
}
obj1.foo();//
obj2.foo();//
obj1.foo.call(obj2);//
obj2.foo.call(obj1);//
new绑定比隐式绑定的优先级高。例:
function foo(something){
this.a=something;
}
var obj1={
foo:foo
}
var obj2={}
obj1.foo(2);
console.log(obj1.a);// obj1.foo.call(obj2,3);
console.log(obj2.a);// var bar=new obj1.foo(4);
console.log(obj1.a);//
console.log(bar.a);//
new和bind比较:
function foo(something){
this.a=something;
}
var obj1={};
var bar=foo.bind(obj1);
bar(2);
console.log(obj1.a);// var baz=new bar(3);
console.log(obj1.a);//
console.log(baz.a);//
4绑定例外;
1被忽略的this:
如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
function foo(){
console.log(this.a);
}
var a=2;
foo.call(null);//
应用:
apply(…)来“展开”一个数组,并当做参数传入一个函数。
bind(…)可以对参数进行柯里化(预先设置一些参数)。
function foo(a,b){
console.log("a: "+a+",b: "+b);
}
//把数组“展开”成参数
foo.apply(null,[2,3]);//a:2,b:3
//使用bind(…)进行柯里化
var bar=foo.bind(null,2);
bar(3);//a:2,b:3
更安全的this:
一种“更安全”的做法是传入一个特殊的对象,把this绑定到这个对象不会对你的程序产生任何副作用。
在JavaScript中创建一个空对象最简单的方法就是Object.create(null)。Object.create(null)和{}很像,但是并不会创建Object.prototype这个委托,所以它比{}“更空”:
function foo(a,b){
console.log("a:"+a+",b:"+b);
}
//我们的DMZ空对象
var ♓=Object.create(null);
//把数组展开成参数
foo.apply(♓,[2,3]);
//使用bind(…)进行柯里化
var bar=foo.bind(♓,2);
bar(3);//a:2,b:3
2间接引用:
function foo(){
console.log(this.a);
}
var a=2;
var o={a:3,foo:foo};
var p={a:4}; o.foo();//
(p.foo=o.foo)();//
赋值表达式p.foo=o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()。根据我们之前所说的,这里会应用默认绑定。
注意:
对于默认绑定来说,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this会被绑定到undefined,否则this会被绑定到全局对象。
3软绑定
可以给默认绑定指定一个全局对象和undefined以外的值。可以实现和硬绑定相同的效果,同时保留隐式绑定或者显示绑定修改this的能力。
if(!Function.prototype.softBind){
Function.prototype.softBind=function(obj){
var fn=this;
//捕获所有的curried参数
var curried=[].slice.call(arguments,1);
var bound=function(){
return fn.apply(
(!this||this===(windwo || global))?obj:this,
curried.concat.apply(curried,arguments)
);
};
bound.prototype=Object.create(fn.prototype);
return bound;
};
}
softBind实现软绑定功能:
function foo(){
console.log("name: "+this.name);
}
var obj={name:"obj"},
obj2={name:"obj2"},
obj3={name:"obj3"};
var fooOBJ=foo.softBind(obj);
fooOBJ();//obj
obj2.foo=foo.softBind(obj);
obj2.foo();//obj2
fooOBJ.call(obj3);//obj3
setTimeout(obj2.foo,100);//obj
5this词法:
箭头函数
箭头函数并不是使用function关键字定义的,而是使用被称为“胖箭头”的操作符=>定义的。箭头函数不使用this的四种规则,而是根据外层(函数或者全局)作用域来决定this(定义函数时)。
function foo(){
//返回一个箭头函数
return (a)=>{
//this继承自foo()
console.log(this.a);
};
}
var obj1={
a:2
};
var obj2={
a:3
};
var bar=foo.call(obj1);
bar.call(obj2);//2 不是3 绑定到创建箭头函数时的this上,绑定无法被修改
foo()内部创建的箭头函数会捕获调用是foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new 也不行!)
箭头函数最常用于回调函数中,例如事件处理器或者定时器。
function foo(){
setTimeout(()=>{
//这里的this在词法上继承自foo()
console.log(this.a);
},100);
}
var obj={
a:2
};
foo.call(obj);//
你不知道的JavaScript——第二章:this全面解析的更多相关文章
- JavaScript 数据访问(通译自High Performance Javascript 第二章) [转]
JavaScript 数据访问(通译自High Performance Javascript 第二章) JavaScript 数据访问(翻译自High Performance Javascript ...
- 你不知道的JavaScript——第一章:作用域是什么?
编译原理 JavaScript事实上是一门编译语言,但与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植. 任何JavaScript代码片段在执行前都要进行编译(通常就在执行 ...
- R语言分析(二)——薛毅R语言第二章后面习题解析
包括2.2—2.6中间的习题,2.2的习题中第三问和第四问,应该有其他的解答方法,但我看他的题目,似乎是在A和B的基础上进行,所以就选择了使用for循环的方法 做着习题,又不断查着书,这样,书籍也熟悉 ...
- JavaScript 第二章总结
Writing real code 设计程序的步骤 First, a high-level design and a flowchart more details Working through th ...
- javascript第二章--变量、作用域和内存问题
① 基本类型和引用类型的值 ② 执行环境及作用域 ③ 垃圾收集
- 你不知道的JavaScript(上)this和对象原型(一)
第一章 关于this 1.this 既不指向函数自身也不指向函数的词法作用域 2.this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用(调用位置). 第二章 this全面 ...
- Javascript权威指南——第二章词法结构,第三章类型、值和变量,第四章表达式和运算符,第五章语句
第二章 词法结构 一.HTML并不区分大小写(尽管XHTML区分大小写),而javascript区分大小写:在HTML中,这些标签和属性名可以使用大写也可以使用小写,而在javascript中必须小写 ...
- Javascript高级程序设计读书笔记(第二章)
第二章 在HTML中使用Javascript 2.1<script>元素 延迟脚本(defer = "defer")表明脚本在执行时不会影响页面的构造,脚本会被延迟到 ...
- 你不知道的javaScript上卷(第一章 作用域是什么)
在写这篇博客时这本书我已经是看过一遍了,为了加深印象和深入学习于是打算做这系列的前端经典书籍导读博文,大家如果觉得这本书讲的好可以自己买来看看,我是比较喜欢看纸质版书的,因为这样才有读书的那种感觉. ...
随机推荐
- html面页与JAVA通过webSocket 通讯
(原) 往常前后端通讯基本都是以ajax请求或是表单做数据交互的,这是一种无状态的http协议,如果要做tcp协议的数据交互,能想到的技术也就socket了,可如果后端是JAVA,前端如何做socke ...
- 1.02-get-params
import urllib.request import urllib.parse import string def get_method_params(): url = "http:// ...
- UVA12545-Bits Equalizer(思维)
Problem UVA12545-Bits Equalizer Accept: 821 Submit: 4548Time Limit: 3000 mSec Problem Description I ...
- [2] TensorFlow 向前传播算法(forward-propagation)与反向传播算法(back-propagation)
TensorFlow Playground http://playground.tensorflow.org 帮助更好的理解,游乐场Playground可以实现可视化训练过程的工具 TensorFlo ...
- BZOJ3577:玩手机(最大流,二维ST表)
Description 现在有一堆手机放在坐标网格里面(坐标从1开始),坐标(i,j)的格子有s_(i,j)个手机. 玩手机当然需要有信号,不过这里的手机与基站与我们不太一样.基站分为两种:发送站和接 ...
- 转载 (三)surging 微服务框架使用系列之我的第一个服务(审计日志)
(三)surging 微服务框架使用系列之我的第一个服务(审计日志) 前言:前面准备了那么久的准备工作,现在终于可以开始构建我们自己的服务了.这篇博客就让我们一起构建自己的第一个服务---审计日志 ...
- zabbix使用percona的mysql监控模板监控
安装第三方插件仓库源: [root@iZ9j3yrx43sb5sZ ~]# yum install https://www.percona.com/redir/downloads/percona-re ...
- 通过随机数获得学生成绩,并把每个元素赋值为学生的分数成绩,通过增强for循环遍历结果。
package com.Summer_0419.cn; /** * @author Summer * 通过随机数获得学生成绩,并把每个元素赋值为学生的分数成绩 */ public class Test ...
- Servlet简单概念和开发小总结
一.Servlet概念 安装了tomcat了解到其实tomcat是一个容器,可以处理用户发送的请求(Request),之后会为请求做出一个响应(Response),此时用户只能请求一些现成的资源,容器 ...
- devops工具-Ansible进阶playbook&roles
一.playbook介绍 playbook 是 Ansible 管理配置.部署应用的核心所在,一个playbook由有多“play组成”,而一个play实际就是一个task,每个task是由多 ...