看JavaScript深入之从ECMAScript规范解读this, 下面梳理下这篇文章的一些知识点:
ECMAScript 的类型分为语言类型和规范类型。
没懂?没关系,我们只要知道在 ECMAScript 规范中还有一种只存在于规范中的类型,它们的作用是用来描述语言底层行为逻辑。
今天我们要讲的重点是便是其中的 Reference 类型。它与 this 的指向有着密切的关联。
要理解this,先要理解Reference,它与this的指向有密切的关系。Reference由三部分组成
而且规范中还提供了获取Reference组成部分的方法,比如 GetBase 和 IsPropertyReference。
规范中还有一个用于从Reference类型获取对应值的方法: GetValue。
GetValue返回对象属性真正的值,但是要注意:调用 GetValue,返回的将是具体的值,而不再是一个 ReferenceMemberExpression 的结果赋值给 ref
MemberExpression就是()左边的部分
```
ref 是不是一个 Reference 类型
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
}
}
// 示例1
console.log(foo.bar());
// MemberExpression 为 foo.bar,foo.bar是一个Reference,将 foo.bar 赋值给 ref
// IsPropertyReference(ref) 为 true,那么 this 的值为 GetBase(ref) ,而 GetBase 返回的是 reference 的 base value。
// 所以 GetBase(ref) => GetBase(foo.bar) => foo.bar的base value => foo
// 所以 this 为 foo !!!
// 所以结果为 2
//示例2
console.log((foo.bar)());
// () 并没有对 MemberExpression 进行计算,所以其实跟示例 1 的结果是一样的
// 结果为 2
//示例3
console.log((foo.bar = foo.bar)());
// 有赋值操作符,使用了 GetValue,所以返回的值不是 Reference 类型
// this 为 undefined,非严格模式下,this 的值为 undefined 的时候,其值会被隐式转换为全局对象。
// 结果为 1
//示例4
console.log((false || foo.bar)());
// 同上,使用了 || 操作符, this 为 undefined
// 结果为 1
//示例5
console.log((foo.bar, foo.bar)());
// 同上,使用了 , 操作符, this 为 undefined
// 结果为 1
function foo() {
console.log(this)
}
foo();
// MemberExpression 是 foo,foo是一个Reference,将 foo 赋值给 ref
// base value 是 EnvironmentRecord,则IsPropertyReference(ref)为false
// 根据上面的判断方法2,this的值为 ImplicitThisValue(ref)
// ImplicitThisValue 始终返回 undefined,所以 this 为 undefined
var obj = {
a: 1,
getA: function(){
alert ( this === obj ); // 输出:true
alert ( this.a ); // 输出: 1
}
};
obj.getA(); // 指向obj
var getA = obj.getA;
getA(); // 指向全局window
var MyClass = function(){
this.name = 'sven';
};
var obj = new MyClass();
alert ( obj.name ); // 输出:sven
但用new 调用构造器时,还要注意一个问题,如果构造器显式地返回了一个object 类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this:
var MyClass = function() {
this.name = 'sven';
return { // 显式地返回一个对象
name: 'anne'
}
};
var obj = new MyClass();
alert ( obj.name ); // 输出:sven
如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成上述问题:
var MyClass = function(){
this.name = 'sven'
return 'anne'; // 返回string 类型
};
obj.b.apply(object, []); // this指向当前的object
JavaScript 的 this 原理 - 阮一峰有内存数据结构和调用环境的讲解,比较简单易懂
call和apply都可以改变函数的执行上下文,调用 call 和 apply 的对象,必须是一个函数 Function
特征:
Function.call(obj,[param1[,param2[,…[,paramN]]]])
使用场景
function superClass () {
this.a = 1;
this.print = function () {
console.log(this.a);
}
}
function subClass () {
superClass.call(this);
this.print();
}
subClass();
// 上面代码等同于
var arr = [].slice.call(arguments);
// ES6:
let arr = Array.from(arguments);
let arr = [...arguments];
// example 1
Array.prototype.forEach.call(arguments,function(item){
console.log(item);
});
// example 2
let domNodes = Array.prototype.slice.call(document.getElementsByTagName("*")); // 这样,domNodes 就可以应用 Array 下的所有方法了。
var a = "abc";
var b = [1,2,3];
Object.prototype.toString.call(a) == "[object String]" //true
Object.prototype.toString.call(b) == "[object Array]" //true
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
模拟实现
Function.prototype.call = function (context) {
context = context || window;
context.fn = this;
let args = [...arguments].slice(1);
let result = context.fn(...args);
delete context.fn
return result;
}
特征:
Function.apply(obj[,argArray])
使用场景
let max = Math.max.apply(null, array);
let min = Math.min.apply(null, array);
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
模拟实现
Function.prototype.apply = function (context, arr) {
context = context || window;
context.fn = this;
let result;
if (!arr) {
result = context.fn();
} else {
result = context.fn(...arr);
}
delete context.fn
return result;
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
bind vs (call & apply)
call() 和 apply() 返回函数应该返回的值,bind() 返回一个经过硬绑定的新函数。
call() 和 apply() 一经调用则立即执行函数,而 bind() 则只是完成了函数的 this 绑定
模拟实现
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
语法
//加括号的函数体返回对象字面量表达式:
params => ({foo: bar})
var func = ()
=> 1;
// SyntaxError: expected expression, got '=>'
let callback;
callback = callback || function() {}; // ok
callback = callback || () => {};
// SyntaxError: invalid arrow-function arguments
callback = callback || (() => {}); // ok
var test = () => { a = 1 }
test() // 运行test会创建全局变量a
console.log(a) // 1
var test = () => { var a = 1 }
test() // 运行test会创建局部变量a
console.log(a); // ReferenceError: now is not defined
高级语法
//支持剩余参数和默认参数
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => {
statements }
//同样支持参数列表解构
let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f(); // 6
// 使用三元运算符
var simple = a => a > 15 ? 15 : a;
箭头函数与普通函数的区别
this,arguments,super或new.target。
箭头函数不能用作Generator函数,不能使用yeild关键字