为什么应用this
先看个例子:
function identity() { return this.name.toUpperCase();}function speak() { return "Hello, i'm " + identity.call(this);}var me = { name: 'rod chen'}var you = { name: "others in Aug"}console.log(identity.call(me)); //ROD CHENconsole.log(identity.call(you)); //OTHERS IN AUGconsole.log(speak.call(me)); //Hello, i'm ROD CHEN console.log(speak.call(you)); //Hello, i'm OTHERS IN AUG
输入的后果很显著,对于call的用法后面文章有提到,第一个参数就是传入到函数里的this的值。这段代码能够在不同的上下文对象( me 和 you )中重复使用函数 identify() 和 speak() ,如果咱们不实用this的话,那就须要identity和speak显示传入一个上下文对象,就像上面的形式
function identity(context) { return context.name.toUpperCase();}function speak(context) { return "Hello, i'm " + identity(context);}var me = { name: 'rod chen'}var you = { name: "others in Aug"}console.log(identity(me));console.log(identity(you));console.log(speak(me));console.log(speak(you));
总结:
this 提供了一种更优雅的形式来隐式“传递”一个对象援用,因而能够将API设计得更加简洁并且易于复用。随着应用模式越来越简单,显式传递上下文对象会让代码变得越来越凌乱,应用 this 则不会这样
Reference
ECMAScript 的类型分为语言类型和标准类型。
ECMAScript 语言类型是开发者间接应用 ECMAScript 能够操作的。其实就是咱们常说的Undefined, Null, Boolean, String, Number, 和 Object。
而标准类型相当于 meta-values,是用来用算法形容 ECMAScript 语言构造和 ECMAScript 语言类型的。标准类型包含:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。
没懂?没关系,咱们只有晓得在 ECMAScript 标准中还有一种只存在于标准中的类型,它们的作用是用来描述语言底层行为逻辑。
Reference 类型就是用来解释诸如 delete、typeof 以及赋值等操作行为的
这里的 Reference 是一个 Specification Type,也就是 “只存在于标准里的形象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于理论的 js 代码中。
组成
这段讲述了 Reference 的形成,由三个组成部分,别离是:
- base value
- referenced name
- strict reference
可是这些到底是什么呢?
咱们简略的了解的话:
base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。
referenced name 就是属性的名称。
var foo = 1;// 对应的Reference是:var fooReference = { base: EnvironmentRecord, name: 'foo', strict: false};
var foo = { bar: function () { return this; }};foo.bar(); // foo// bar对应的Reference是:var BarReference = { base: foo, propertyName: 'bar', strict: false};
办法
- GetBase:返回 reference 的 base value。
- IsPropertyReference:简略的了解:如果 base value 是一个对象,就返回true。
- GetValue:返回具体的值
如何确定this的值
function call步骤
- 令 ref 为解释执行 MemberExpression 的后果 .
- 令 func 为 GetValue(ref).
- 令 argList 为解释执行 Arguments 的后果 , 产生参数值们的外部列表 (see 11.2.4).
- 如果 Type(func) is not Object ,抛出一个 TypeError 异样 .
- 如果 IsCallable(func) is false ,抛出一个 TypeError 异样 .
- 如果 Type(ref) 为 Reference,那么 如果 IsPropertyReference(ref) 为 true,那么 令 thisValue 为 GetBase(ref). 否则 , ref 的基值是一个环境记录项 令 thisValue 为调用 GetBase(ref) 的 ImplicitThisValue 具体方法的后果
- 否则 , 如果 Type(ref) 不是 Reference. 令 thisValue 为 undefined.
- 返回调用 func 的 [[Call]] 内置办法的后果 , 传入 thisValue 作为 this 值和列表 argList 作为参数列表
MemberExpression
- PrimaryExpression // 原始表达式 能够参见《JavaScript权威指南第四章》
- FunctionExpression // 函数定义表达式
- MemberExpression [ Expression ] // 属性拜访表达式
- MemberExpression . IdentifierName // 属性拜访表达式
- new MemberExpression Arguments // 对象创立表达式
这里说的是办法调用的右边局部。
function foo() { console.log(this)}foo(); // MemberExpression 是 foofunction foo() { return function() { console.log(this) }}foo()(); // MemberExpression 是 foo()var foo = { bar: function () { return this; }}foo.bar(); // MemberExpression 是 foo.bar
判断ref的类型
第一步计算ref,第七步须要判断ref是不是一个reference类型。
var value = 1;var foo = { value: 2, bar: function () { return this.value; }}//示例1console.log(foo.bar());//示例2console.log((foo.bar)());//示例3console.log((foo.bar = foo.bar)());//示例4console.log((false || foo.bar)());//示例5console.log((foo.bar, foo.bar)());
foo.bar()
这个是属性拜访。依据11.2.1 property accessors最初一步:
Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.
返回了一个reference类型:返回一个reference类型的援用,其基值为 baseValue 且其援用名为 propertyNameString, 严格模式标记为 strict
var Reference = { base: foo, name: 'bar', strict: false};
而后回到function call第六、七步,
- 如果 Type(ref) 为 Reference,那么 如果 IsPropertyReference(ref) 为 true,那么 令 thisValue 为 GetBase(ref). 否则 , ref 的基值是一个环境记录项 令 thisValue 为调用 GetBase(ref) 的 ImplicitThisValue 具体方法的后果
- 否则 , 如果 Type(ref) 不是 Reference. 令 thisValue 为 undefined.
这里因为foo是一个对象,所以这里IsPropertyReference(ref) 的值为true。所以这里this的值就是GetBase(ref)就是foo。 参考视频解说:进入学习
(foo.bar)()
分组表达式规定如下:
- 返回执行Expression的后果,它可能是Reference类型
这里的后果和下面雷同都是fo。
(foo.bar = foo.bar)()
波及到简略赋值,规定如下:
- 令 lref 为解释执行 LeftH 和 SideExpression 的后果 .
- 令 rref 为解释执行 AssignmentExpression 的后果 .
- 令 rval 为 GetValue(rref).
抛出一个 SyntaxError 异样,当以下条件都成立 :
- Type(lref) 为 Reference
- IsStrictReference(lref) 为 true
- Type(GetBase(lref)) 为环境记录项
- GetReferencedName(lref) 为 "eval" 或 "arguments"
- 调用 PutValue(lref, rval).
- 返回 rval.
这里的返回值为第三部,GetValue。这是一个具体的返回值。依据下面的值,失去this的值为undefined,非严格模式下这里隐式装换为window对象。
(false || foo.bar)()和(foo.bar, foo.bar)()
这里的返回值都是去的getValue的值。所以this都和下面一样。
看一下最终的后果:
var value = 1;var foo = { value: 2, bar: function () { return this.value; }}//示例1console.log(foo.bar()); // 2//示例2console.log((foo.bar)()); // 2//示例3console.log((foo.bar = foo.bar)()); // 1//示例4console.log((false || foo.bar)()); // 1//示例5console.log((foo.bar, foo.bar)()); // 1
一般函数
function foo() { console.log(this)}foo();
这种属于解析标识符
The result of evaluating an identifier is always a value of type Reference with its referenced name component equal to the Identifier String.
解释执行一个标识符失去的后果必然是 援用 类型的对象,且其援用名属性的值与 Identifier 字符串相等。
那么baseValue是什么值呢?因为解析标识符会调用的后果是GetIdentifierReference。
如果 lex 的值为 null,则:
- 返回一个类型为 援用 的对象,其基值为 undefined,援用的名称为 name,严格模式标识的值为 strict。
- 令 envRec 为 lex 的环境数据。
- 以 name 为参数 N,调用 envRec 的 HasBinding(N) 具体方法,并令 exists 为调用的后果。
如果 exists 为 true,则:
- 返回一个类型为 援用 的对象,其基值为 envRec,援用的名称为 name,严格模式标识的值为 strict。
否则:
- 令 outer 为 lex 的 外部环境援用 。
- 以 outer、name 和 struct 为参数,调用 GetIdentifierReference,并返回调用的后果。
这里因为是window对象所以这里返回的是:
var fooReference = { base: EnvironmentRecord, // 或者这里是undefined name: 'foo', strict: false};
不论base的值是下面两种的哪一种,那都不是Object。那依据下面的规定:
- 如果 Type(ref) 为 Reference,那么 如果 IsPropertyReference(ref) 为 true,那么 令 thisValue 为 GetBase(ref). 否则 , ref 的基值是一个环境记录项 令 thisValue 为调用 GetBase(ref) 的 ImplicitThisValue 具体方法的后果
- 否则 , 如果 Type(ref) 不是 Reference. 令 thisValue 为 undefined.
this的值为ImplicitThisValue的值。这个值返回的始终undefined。所以这里this的是undefined。
什么是this
说的是执行上下文的thisbinding。
this说的是以后函数的调用地位。这个是概念形容。上面通过下面的只是去剖析各种状况下的thisbinding是什么货色。
this 提供了一种更优雅的形式来隐式“传递”一个对象援用,因而能够将API设计得更加简洁
并且易于复用。随着应用模式越来越简单,显式传递上下文对象会让代码变得越来越凌乱,应用 this 则不会这样
函数调用
具体参照下面说的一般函数
call,apply
var person = { name: "axuebin", age: 25};function say(job){ console.log(this.name+":"+this.age+" "+job);}say.call(person,"FE"); // axuebin:25say.apply(person,["FE"]); // axuebin:25
call
Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] ) 当以 thisArg 和可选的 arg1, arg2 等等作为参数在一个 func 对象上调用 call 办法,采纳如下步骤:
- 如果 IsCallable(func) 是 false, 则抛出一个 TypeError 异样。
- 令 argList 为一个空列表。
- 如果调用这个办法的参数多余一个,则从 arg1 开始以从左到右的程序将每个参数插入为 argList 的最初一个元素。
- 提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 外部办法,返回后果。
call 办法的 length 属性是 1。
this的值为传入thisArg的值。
apply
- 如果IsCallable(func) 是 false, 则抛出一个 TypeError 异样
如果 argArray 是 null 或 undefined, 则
- 返回提供 thisArg 作为 this 值并以空参数列表调用 func 的 [[Call]] 外部办法的后果。
- 如果 Type(argArray) 不是 Object, 则抛出一个 TypeError 异样 .
- 令 len 为以 "length" 作为参数调用 argArray 的 [[Get]] 外部办法的后果。
- 令 n 为 ToUint32(len).
- 令 argList 为一个空列表 .
- 令 index 为 0.
只有 index < n 就反复
- 令 indexName 为 ToString(index).
- 令 nextArg 为以 indexName 作为参数调用 argArray 的 [[Get]] 外部办法的后果。
- 将 nextArg 作为最初一个元素插入到 argList 里。
- 设定 index 为 index + 1.
- 提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 外部办法,返回后果。
apply 办法的 length 属性是 2。
留神 在里面传入的 thisArg 值会批改并成为 this 值。thisArg 是 undefined 或 null 时它会被替换成全局对象,所有其余值会被利用 ToObject 并将后果作为 this 值,这是第三版引入的更改。
function [[call]]
因为下面说了调用function的[[Call]]外部办法。
当用一个 this 值,一个参数列表调用函数对象 F 的 [[Call]] 外部办法,采纳以下步骤:
- 用 F 的 [[FormalParameters]] 外部属性值,参数列表 args,10.4.3 形容的 this 值来建设 函数代码 的一个新执行环境,令 funcCtx 为其后果。
- 令 result 为 FunctionBody(也就是 F 的 [[Code]] 外部属性)解释执行的后果。如果 F 没有 [[Code]] 外部属性或其值是空的 FunctionBody,则 result 是 (normal, undefined, empty)。
- 退出 funcCtx 执行环境,复原到之前的执行环境。
- 如果 result.type 是 throw 则抛出 result.value。
- 如果 result.type 是 return 则返回 result.value。
- 否则 result.type 必然是 normal。返回 undefined。
这里又提到了10.4.3的执行函数代码的规定:
当控制流依据一个函数对象 F、调用者提供的 thisArg 以及调用者提供的 argumentList,进入 函数代码 的执行环境时,执行以下步骤:
- 如果 函数代码 是 严格模式下的代码 ,设 this 绑定为 thisArg。
- 否则如果 thisArg 是 null 或 undefined,则设 this 绑定为 全局对象 。
- 否则如果 Type(thisArg) 的后果不为 Object,则设 this 绑定为 ToObject(thisArg)。
- 否则设 this 绑定为 thisArg。
- 以 F 的 [[Scope]] 外部属性为参数调用 NewDeclarativeEnvironment,并令 localEnv 为调用的后果。
- 设词法环境为 localEnv。
- 设变量环境为 localEnv。
- 令 code 为 F 的 [[Code]] 外部属性的值。
- 按 10.5 形容的计划,应用 函数代码 code 和 argumentList 执行定义绑定初始化步骤。
bind
var person = { name: "axuebin", age: 25};function say(){ console.log(this.name+":"+this.age);}var f = say.bind(person);console.log(f());
外面同样的也是讲传入的thisArg设置this的值。
箭头函数
箭头函数并不绑定 this,arguments,super(ES6),或者 new.target(ES6),这些常识沿用蕴含以后箭头函数的关闭的词法环境。
function foo() { setTimeout( () => { console.log("args:", arguments); },100);}foo( 2, 4, 6, 8 );// args: [2, 4, 6, 8]
说法定义纠正
- 箭头函数的this是绑定到父函数foo的,其实不是,只是沿用。因为箭头函数外部没有做任何的绑定操作。
- 局部变量this 上面的例子是为了阐明局部变量this
function foo() { var self = this; setTimeout(function() { console.log("id:" + this.id); },100);}foo.call( { id: 42 } );// id:undefined
这里为什么是undefined,因为这里setTimeout是windows对象的属性。this指向window。
而后批改了一下:
function foo() { var self = this; setTimeout(function() { console.log("id:" + self.id); },100);}foo.call( { id: 42 } );// id:42
这里是因为self对于function的执行是一个执行上下文变量环境outer指向的蕴含以后函数的闭合函数变量环境。这里和this没有任何关系。
这里提一下:“因为 this 无论如何都是部分的”。this都是函数执行的this绑定规定来决定的。
作为对象的一个办法
参照:上文foo.bar函数调用的解析
作为一个构造函数
[[construct]]
- 令 obj 为新创建的 ECMAScript 原生对象。
- 按照 8.12 设定 obj 的所有外部办法。
- 设定 obj 的 [[Class]] 外部办法为 "Object"。
- 设定 obj 的 [[Extensible]] 外部办法为 true。
- 令 proto 为以参数 "prototype" 调用 F 的 [[Get]] 外部属性的值。
- 如果 Type(proto) 是 Object,设定 obj 的 [[Prototype]] 外部属性为 proto。
- 如果 Type(proto) 不是 Object,设定 obj 的 [[Prototype]] 外部属性为 15.2.4 形容的规范内置的 Object 的 prototype 对象。
- 以 obj 为 this 值,调用 [[Construct]] 的参数列表为 args,调用 F 的 [[Call]] 外部属性,令 result 为调用后果。
- 如果 Type(result) 是 Object,则返回 result。
- 返回 obj
咱们能够看到this绑定在以后创立的对象。
作为一个DOM事件处理函数
this指向触发事件的元素,也就是始事件处理程序所绑定到的DOM节点。
var ele = document.getElementById("id");ele.addEventListener("click",function(e){ console.log(this); console.log(this === e.target); // true})
这里可能不太好晓得。从文献中理解到:
The event listener is appended to target’s event listener list
interface EventTarget { constructor(); undefined addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options = {}); undefined removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options = {}); boolean dispatchEvent(Event event);};callback interface EventListener { undefined handleEvent(Event event);};dictionary EventListenerOptions { boolean capture = false;};dictionary AddEventListenerOptions : EventListenerOptions { boolean passive = false; boolean once = false;};
依据EventTarget的属性,能够看到callback是一级属性,所以Event.callback的执行的this指向的是EventTarget。当然具体的实现没有看到
从侧面理解:还有一种写法:button.onclick = foo。这种就能够很好的理解了。
this优先级
当初咱们能够依据优先级来判断函数在某个调用地位利用的是哪条规定。能够依照上面的程序来进行判断:
- 函数是否在 new 中调用( new 绑定)?如果是的话 this 绑定的是新创建的对象。
var bar = new foo()
- 函数是否通过 call 、 apply (显式绑定)或者硬绑定调用?如果是的话, this 绑定的是指定的对象。
var bar = foo.call(obj2)
- 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话, this 绑定的是那个上下文对象。
var bar = obj1.foo()
- 如果都不是的话,应用默认绑定。如果在严格模式下,就绑定到 undefined ,否则绑定到全局对象。
var bar = foo()
论断:new 调用 > call、apply、bind 调用 > 对象上的函数调用 > 一般函数调用
// 例子起源:若风 https://juejin.cn/post/6844903746984476686#heading-12var name = 'window';var person = { name: 'person',}var doSth = function(){ console.log(this.name); return function(){ console.log('return:', this.name); }}var Student = { name: 'rod', doSth: doSth,}// 一般函数调用doSth(); // window// 对象上的函数调用Student.doSth(); // 'rod'// call、apply 调用Student.doSth.call(person); // 'person'new Student.doSth.call(person); // Uncaught TypeError: Student.doSth.call is not a constructor
最初这一行说一下,因为. 运算符的优先级高于new。所以这里是Student.doSth.call作为new的构造函数。然而因为call的办法执行的时候,执行的是func的[[call]]办法。想要吊用new的话须要调用[[Construct]] 属性
call的调用
当以 thisArg 和可选的 arg1, arg2 等等作为参数在一个 func 对象上调用 call 办法,采纳如下步骤:
- 如果 IsCallable(func) 是 false, 则抛出一个 TypeError 异样。
- 令 argList 为一个空列表。
- 如果调用这个办法的参数多余一个,则从 arg1 开始以从左到右的程序将每个参数插入为 argList 的最初一个元素。
- 提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 外部办法,返回后果。