Appearance
函数家族
普通函数:用 function 关键字定义的函数。
function foo(){
// code
}
箭头函数:用 => 运算符定义的函数。
const foo = () => {
// code
}
方法:在 class 中定义的函数。
class C {
foo(){
//code
}
}
类:用 class 定义的类,实际上也是函数。
class Foo {
constructor(){
//code
}
}
异步函数:普通函数、箭头函数和生成器函数加上 async 关键字。
async function foo(){
// code
}
const foo = async () => {
// code
}
async function foo*(){
// code
}
this 关键字的行为
this 是执行上下文中很重要的一个组成部分。同一个函数调用方式不同,得到的 this 值 也不同
function showThis(){
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // global
o.showThis(); // o
调用函数时使用的引用,决定了函数执行时刻的 this 值。
剪头函数中的 this
const showThis = () => {
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // global
o.showThis(); // global
class 中的 this
class C {
showThis() {
console.log(this);
}
}
var o = new C();
var showThis = o.showThis;
showThis(); // undefined
o.showThis(); // o
等同于严格模式下:
"use strict"
function showThis(){
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // undefined
o.showThis(); // o
为什么输出 undefined?
答:因为 class 设计成了默认按 strict 模式执行,this 严格按照调用时传入的值,可能 为 null 或者 undefined。
JavaScript 用一个栈来管理执行上下文,这个栈中的每一项又包含一个链表。如下图所示 :
当函数调用时,会入栈一个新的执行上下文,函数调用结束时,执行上下文被出栈
而 this 则是一个更为复杂的机制,JavaScript 标准定义了 [[thisMode]] 私有属性。
[[thisMode]] 私有属性有三个取值。
- lexical:表示从上下文中找 this,这对应了箭头函数。
- global:表示当 this 为 undefined 时,取全局对象,对应了普通函数。
- strict:当严格模式时使用,this 严格按照调用时传入的值,可能为 null 或者 undefined。
函数创建新的执行上下文中的词法环境记录时,会根据[[thisMode]]来标记新纪录 的[[ThisBindingStatus]]私有属性。
代码执行遇到 this 时,会逐层检查当前词法环境记录中的[[ThisBindingStatus]],当找 到有 this 的环境记录时获取 this 的值。
这样的规则的实际效果是,嵌套的箭头函数中的代码都指向外层 this,例如:
var o = {}
o.foo = function foo(){
console.log(this);
return () => {
console.log(this);
return () => console.log(this);
}
}
o.foo()()(); // o, o, o
这个例子中,我们定义了三层嵌套的函数,最外层为普通函数,两层都是箭头函数。这里调 用三个函数,获得的 this 值是一致的,都是对象 o。
操作 this 的内置函数
Function.prototype.call 和 Function.prototype.apply
可以指定函数调用时传入 的 this 值,示例如下:
//这里 call 和 apply 作用是一样的,只是传参方式有区别。会立即执行
function foo(a, b, c){
console.log(this); //如果传进来的this是null或者undefined,那么将会输出global
console.log(a, b, c);
}
foo.call({}, 1, 2, 3);
foo.apply({}, [1, 2, 3]);
//bind
function foo(a, b, c){
console.log(this);
console.log(a, b, c);
}
foo.bind({}, 1, 2, 3)();
相似之处
1、都是用来改变函数的 this 对象的指向的。
2、第一个参数都是 this 要指向的对 象。
3、都可以利用后续参数传参。
区别
1.call、apply 与 bind 都用于改变 this 绑定,但 call、apply 在改变 this 指向的同 时还会执行函数,而 bind 在改变 this 后是返回一个全新的 boundFcuntion 绑定函数, 这也是为什么上方例子中 bind 后还加了一对括号 ()的原因。
2.bind 属于硬绑定,返回的 boundFunction 的 this 指向无法再次通过 bind、apply 或 call 修改;call 与 apply 的绑定只适用当前调用,调用完就没了,下次要用还得再次绑 。
3.call 与 apply 功能完全相同,唯一不同的是 call 方法传递函数调用形参是以散列形式 ,而 apply 方法的形参是一个数组。在传参的情况下,call 的性能要高于 apply,因为 apply 在执行时还要多一步解析数组。
wx.say.bind(this) 不能立即执行,无效,必须 wx.say.bind(this)("aaa"),参数置 后
wx.say.call(this,"aaa","bbb") 立即执行
wx.say.apply(this,["aaa","bbb"]) 立即执行,参数为数组
手写 call,apply,bind
Function.prototype.myCall =
function (ctx) {
ctx = ctx || window;
ctx.fn = this;
let args = Array.from(arguments).slice(1);
let res = ctx.fn(...args);
delete ctx.fn;
return res;
};
Function.prototype.myApply = function (ctx) {
ctx = ctx || window;
ctx.fn = this;
let args = Array.from(arguments[1]);
let res = ctx.fn(...args);
delete ctx.fn;
return res;
};
Function.prototype.myBind = function (ctx) {
let args = Array.from(arguments).slice(1);
let that = this;
return function (...oargs)
{
return that.apply(ctx, [...args, ...oargs]);
};
};