函数调用与this 绑定
函数调用与this 绑定
函数执行
我们已经知道执行栈
之中。
如果你在当前函数里面又调用了另外一个函数执行上下文
,它被压入现有栈的顶部。浏览器永远会执行当前栈中顶部的执行上下文一旦函数在当前
执行上下文执行完毕,它会被从栈的顶部弹出,然后将控制权移交给当前栈的下一个上下文当中。下面的代码展示了一个递归函数以及程序的执行上下文
(function foo(i) {
if (i === 3) {
return;
} else {
foo(++i);
}
})(0);
console.log(1);
(_ => console.log(2))();
eval('console.log(3);');
console.log.call(null, 4);
console.log.apply(null, [5]);
new Function('console.log(6)')();
Reflect.apply(console.log, null, [7])
Reflect.construct(function(){console.log(8)}, []);
Function.prototype.apply.call(console.log, null, [9]);
Function.prototype.call.call(console.log, null, 10);
new (require('vm').Script)('console.log(11)‘).runInThisContext();
function createNamedFunction(name, fn) {
return new Function(
'f',
return function ${name}(){ return f.apply(this,arguments)}`
)(fn);
}
let func = createNamedFunction('namedFunction', () => {
console.log('namedFunction');
});
console.log(func);
func();

这段代码调用自己自身
关于 执行上下文 有五个要点是要记住的
-
单线程。
-
同步执行。
-
只有一个全局上下文。
-
可有无数个函数上下文。
-
每个函数调用都会创建一个新的执行上下文
` ,哪怕是递归调用。
执行上下文中的细节
现在我们已经知道了每个函数调用都会创建一个新的执行上下文。然而,在
我们可以用一个具有三个属性的概念性对象来代表执行上下文
executionContextObj = {
'scopeChain': { /* 变量对象 + 所有父级执行上下文中的变量对象 */ },
'variableObject': { /* 函数参数 / 参数, 内部变量以及函数声明 */ },
'this': {}
}
活动对象/ 变量对象[AO/VO]
这个executionContextObj对象在函数调用的时候创建, 但是实在函数真正执行之前。这就是我们所说的第 1 阶段创建阶段
。在这个阶段,解释器通过扫描传入函数的参数,局部函数声明和局部变量声明来创建
这是解释器执行代码时的伪概述
-
寻找调用函数的代码
-
在执行函数代码之前
, 创建执行上下文`. -
进入创建阶段
-
初始化
作用域链
. -
创建
变量对象
: -
创建参数对象
`, 检查参数的上下文, 初始化其名称和值并创建一个引用拷贝。 -
扫描上下文中的函数声明:
-
对于每个被发现的函数
, 在变量对象中创建一个和函数名同名的属性,这是函数在内存中的引用。 -
如果函数名已经存在
, 引用值将会被覆盖。 -
扫描上下文中的变量声明:
-
对于每个被发现的变量声明
, 在变量对象
中创建一个同名属性并初始化值为 undefined。 -
如果变量名在变量对象中已经存在
, 什么都不做,继续扫描。 -
确定上下文中的
"this"
-
激活
/ 代码执行阶段: -
执行
/ 在上下文中解释函数代码,并在代码逐行执行时给变量赋值。
让我们来看一个例子
function foo(i) {
const a = "hello";
const b = function privateB() {};
function c() {}
}
foo(22);
在调用
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}
你可以发现
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
this: { ... }
}
bind
Function.prototype.bind = function() {
const fn = this,
args = Array.prototype.slice.call(arguments),
object = args.shift();
return function() {
return fn.apply(object, args.concat(Array.prototype.slice.call(arguments)));
};
};
JavaScript this
在
Default this: 默认情况下this 的指向
Global Context( 全局上下文)
在任何函数之外的全局环境中,不管在不在
console.log(this.document === document); // true
// In web browsers, the window object is also the global object:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
Simple Function Context( 简单函数上下文)
在某个函数中,hello("world”)
还是
function hello(thing) {
console.log("Hello " + thing);
}
// this:
hello("world");
// 编译为
hello.call(window, "world");
而如果是
// this:
hello("world");
// 编译为
hello.call(undefined, "world");
DOM Event handler(DOM 事件)
当某个函数作为事件监听器时,它的
// When called as a listener, turns the related element blue
function bluify(e){
// Always true
console.log(this === e.currentTarget);
// true when currentTarget and target are the same object
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// Get a list of every element in the document
const elements = document.getElementsByTagName('*');
// Add bluify as a click listener so when the
// element is clicked on, it turns blue
for(const i=0 i<elements.length i++){
elements[i].addEventListener('click', bluify, false);
}
如果是行内的事件监听者,
<button onclick="alert(this.tagName.toLowerCase());">Show this</button>
Manual Setting: 手动指定this
Closures( 闭包)
const asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
// Define a reference tothisoutside of the callback,
// but within the callback's lexical scope
const o = {
doSomething: function () {
const self = this;
// Here we passointo the async function,
// expecting it back asparam`
asyncFunction(o, function (param) {
console.log("param === this?", param === self);
});
},
};
o.doSomething(); // param === this? true
对象方法
如果将某个方法设置为
function hello(thing) {
console.log(this + " says hello " + thing);
}
person = { name: "Brendan Eich" };
person.hello = hello;
person.hello("world"); // still desugars to person.hello.call(person, "world") [object Object] says hello world
hello("world"); // "[object DOMWindow]world"
这种效果等效于使用
call/apply: 运行时指定
const Cat = function (name) {
this.name = name;
};
const Dog = function (name) {
this.name = name;
};
Cat.prototype.sayHi = function () {
console.log(`${this.name} meows loudly!`);
};
Dog.prototype.sayHi = function () {
console.log(`${this.name} barks excitedly!`);
};
const whiskers = new Cat("whiskers");
const fluffybottom = new Dog("fluffy bottom");
whiskers.sayHi(); // => whiskers meows loudly!
fluffybottom.sayHi(); // => fluffy bottom barks excitedly!
Cat.prototype.sayHi.call(fluffybottom); // => fluffy bottom meows loudly!
whiskers.sayHi.call(fluffybottom); // => fluffy bottom meows loudly!
Dog.prototype.sayHi.call(whiskers); // => whiskers barks excitedly!
fluffybottom.sayHi.call(whiskers); // => whiskers barks excitedly!
bind: 绑定
+-------------------+-------------------+
| | |
| time of | time of |
|function execution | this binding |
| | |
+-------------------+-------------------+-------------------+
| | | |
| function object | future | future |
| f | | |
| | | |
+-------------------+-------------------+-------------------+
| | | |
| function call | now | now |
| f() | | |
| | | |
+-------------------+-------------------+-------------------+
| | | |
| f.call() | now | now |
| f.apply() | | |
| | | |
+-------------------+-------------------+-------------------+
| | | |
| f.bind() | future | now |
| | | |
+-------------------+-------------------+-------------------+
很多时候,需要为某个函数指定一个固定的
Thebind()method creates a new function that, when called, has itsthiskeyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
其作用可以用下面一个例子进行说明:
this.x = 9;
const module = {
x: 81,
getX: function () {
return this.x;
},
};
module.getX(); // 81
const getX = module.getX;
getX(); // 9, because in this case, "this" refers to the global object
// Create a new function with 'this' bound to module
const boundGetX = getX.bind(module);
boundGetX(); // 81
const myObj = {
specialFunction: function () {},
anotherSpecialFunction: function () {},
getAsyncData: function (cb) {
cb();
},
render: function () {
const that = this;
this.getAsyncData(function () {
that.specialFunction();
that.anotherSpecialFunction();
});
},
};
myObj.render();
如果在that.specialFunction();
,是会得到如下的错误显示:
Uncaught TypeError: Object [object global] has no method ‘specialFunction’
可以将代码以如下方式重写:
render: function () {
this.getAsyncData(function () {
this.specialFunction();
this.anotherSpecialFunction();
}.bind(this));
}
Browser | Version support |
---|---|
Chrome | 7 |
Firefox (Gecko) | 4.0 (2) |
Internet Explorer | 9 |
Opera | 11.60 |
Safari | 5.1.4 |
const person = {
name: "Brendan Eich",
hello: function (thing) {
console.log(this.name + " says hello " + thing);
},
};
const boundHello = function (thing) {
return person.hello.call(person, thing);
};
boundHello("world");
不过,这种方式仍然存在着一定的问题,
fun.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg 当绑定函数被调用时,该参数会作为原函数运行时的this 指向。当使用new 操作符调用绑定函数时,该参数无效。- arg1, arg2, … 当绑定函数被调用时,这些参数加上绑定函数本身的参数会按照顺序作为原函数运行时的参数。
const boundHello = person.hello.bind(person);
boundHello("world"); // "Brendan Eich says hello world"
这种方式在设置回调函数中的
const person = {
name: "Alex Russell",
hello: function () {
console.log(this.name + " says hello world");
},
};
$("#some-div").click(person.hello.bind(person));
// when the div is clicked, "Alex Russell says hello world" is printed
const asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
// Here we control the context of the callback using
//bindensuringthisis correct
const o = {
doSomething: function () {
// Here we passointo the async function,
// expecting it back asparam`
asyncFunction(
o,
function (param) {
console.log("param === this?", param === this);
}.bind(this)
);
},
};
o.doSomething(); // param === this? true
还有一个类似的实例是
const o = {
v: "hello",
p: ["a1", "a2"],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + " " + item);
});
},
};
o.f();
//undefined a1
//undefined a2
Arrow Function 绑定
在
const asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
const o = {
doSomething: function () {
// Here we passointo the async function,
// expecting it back asparam`.
//
// Because this arrow function is created within
// the scope ofdoSomethingit is bound to this
// lexical scope.
asyncFunction(o, (param) => {
console.log("param === this?", param === this);
});
},
};
o.doSomething(); // param === this? true