参考网站
https://www.cnblogs.com/zhg277245485/p/6559475.html
https://www.jianshu.com/p/bc541afad6ee
改变作用域原理
方法调用模式:
var a = 1
var obj1 = {
a:2,
fn:function(){ // 当一个函数被保存为对象的一个方法
console.log(this.a)
}
}
obj1.fn()//2 //如果调用表达式包含一个提取属性的动作(即被‘()’),那么它就是被当做一个方法来调用
// 此时的this被绑定到这个对象。
此时的this是指obj1这个对象,obj1.fn()
实际上是obj1.fn.call(obj1)
,事实上谁调用这个函数,this就是谁。补充一下,DOM对象绑定事件也属于方法调用模式,因此它绑定的this就是事件源DOM对象。如:
document.addEventListener('click', function(e){
console.log(this);
setTimeout(function(){
console.log(this);
}, 200);
}, false);
点击页面,依次输出:document和window对象
解析:点击页面监听click事件属于方法调用,this指向事件源DOM对象,即obj.fn.apply(obj)
,setTimeout内的函数属于回调函数,可以这么理解,f1.call(null,f2)
,所以this指向window。
函数调用模式:
就是普通函数的调用,此时的this被绑定到window
- 最普通的函数调用
function fn1(){
console.log(this)//window
}
fn1()
- 函数嵌套
function fn1(){
function fn2(){
console.log(this)//window
}
fn2()
}
fn1()
- 把函数赋值之后再调用
var a = 1
var obj1 = {
a:2,
fn:function(){
console.log(this.a)
}
}
var fn1 = obj1.fn // 等价于 var fn1 = console.log(this.a)
fn1()//1
obj1.fn是一个函数function(){console.log(this.a)}
,此时fn1就是不带任何修饰的函数调用,function(){console.log(this.a)}.call(undefined)
,按理说打印出来的 this 应该就是 undefined 了吧,但是浏览器里有一条规则:
如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)
因此上面的this绑定的就是window,它也被称为隐性绑定。
如果你希望打印出2,可以修改fn1()
为fn1.call(obj1)
,显示地绑定this为obj1
- 回调函数
var a = 1
function f1(fn){
fn()
console.log(a)//1
}
f1(f2)
function f2(){
var a = 2
}
改写代码如下:
var a = 1
function f1(){
(function (){var a = 2})()
console.log(a)//1
}
f1()
仍旧是最普通的函数调用,f1.call(undefined)
,this指向window,打印出的是全局的a。
借此,我们终于可以解释为什么setTimeout总是丢失this了,因为它也就是一个回调函数而已。
setTimeout(function() {
console.log(this)//window
function fn(){
console.log(this)//window
}
fn()
}, 0);
构造器调用模式:
new一个函数时,背地里会将创建一个连接到prototype成员的新对象,同时this会被绑定到那个新对象上
function Person(name,age){
// 这里的this都指向实例
this.name = name
this.age = age
this.sayAge = function(){
console.log(this.age)
}
}
var dot = new Person('Dot',2)
dot.sayAge()//2
Call()与apply(),bind() 语法以及含义
banana = {
color: 'yellow'
}
apple.say.call(banana); //此时的this的指向已经同过call()方法改变了,apple内的this指向的是banana,this.color就是banana.color='yellow';
//结果是My color is yellow
apple.say.apply(banana);//同理,此时的this的指向已经同过apply()方法改变了,apple内的this指向的是banana,this.color就是banana.color ='yellow';
//My color is yellow
// 如果传入的是 null:
apple.say.apply(null); // null是window下的,此时,apple内的this 就指向了window ,但是window下并没有clolr这个属性,因此this.clolr就是window.color=undefined;
//My color is undefined
语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
语法:apply([thisObj[,argArray]]) // 第二个参数[,argArray]],就是要传入的参数(且一定要为数组形式)
语法:bind([thisObj[,argArray]])
bind()
方法会创建一个 新函数,称为绑定函数,当调用这个绑定函数时,
绑定函数会以创建它时传入 bind()
方法的第一个参数 作为 this
,传入 bind()
方法的 第二个以及以后的参
数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
bar(); // undefined
var func = bar.bind(foo); //此时this已经指向了foo,但是用bind()方法并不会立即执行,而是创建一个新函数,如果要直接调用的话 可以 bar.bind(foo)()
func(); // 3
bind参数的使用
function fn(a, b, c) {
console.log(a, b, c);
}
var fn1 = fn.bind(null, 'Dot');
fn('A', 'B', 'C'); // A B C
fn1('A', 'B', 'C'); // Dot A B // 会保留‘dot’作为第一个参数(默认参数)
fn1('B', 'C'); // Dot B C
fn.call(null, 'Dot'); // Dot undefined undefined
bind注意点
1.在 Javascript 中,多次 bind()
是无效的(只执行第一个bind)。更深层次的原因, bind()
的实现,相当于使用函数在内部包了一个 call / apply
,第二次 bind()
相当于再包住第一次 bind()
,故第二次以后的 bind
是无法生效的。
var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
var sed = {
x:4
}
var func = bar.bind(foo).bind(sed);
func(); //3
var fiv = {
x:5
}
var func = bar.bind(foo).bind(sed).bind(fiv);
func(); //3
call
、apply
和bind
相同点与区别
相同
call
、apply
和bind
是Function
对象自带的三个方法,都是为了改变函数体内部 this
的指向。
apply 、 call 、bind
三者第一个参数都是 this
要指向的对象,也就是想指定的上下文;
function add(a,b){
return a+b;
}
function sub(a,b){
return a-b;
}
var a1 = add.apply(sub,[4,2]); //sub调用add的方法
var a2 = sub.apply(add,[4,2]);
alert(a1); //6
alert(a2); //2
/*call的用法*/
var a1 = add.call(sub,4,2); // 参数要按照add的参数顺序,并且个数一样
apply 、 call 、bind
三者都可以利用后续参数传参;
区别:
apply 、call
传参形式不同 。call 是把参数按顺序传递进去,而 apply 则是把参数放在数组 里。
bind 是返回对应 函数,便于稍后调用;apply 、call
则是立即调用 。
var obj = {
x: 81,
};
var foo = {
getX: function() {
return this.x;
}
}
console.log(foo.getX.bind(obj)()); //81 // bind返回一个函数,需要执行
console.log(foo.getX.call(obj)); //81
console.log(foo.getX.apply(obj)); //81
callee
callee返回正callee所在的函数本身,它是arguments的一个属性
使用callee时要注意:
1 这个属性只有在函数执行时才有效
2 它有一个length属性,可以用来获得形参的个数,因此可以用来比较形参和实参个数是否一致,即比较arguments.length是否等于arguments.callee.length
3 它可以用来递归匿名函数。
var a = function() {
alert(arguments.callee);
}
var b = function() {
a();
}
b();
a在b中被调用,但是它返回了a本身的引用,结果如下:
caller
caller返回一个函数的引用,这个函数调用了当前的函数。
问题集锦