call()
- 表示立即执行函数;
- 如果有一个参数,且该参数为对象类型,则表示将函数中 this 的指向改变为这里带入的参数;
- 函数中如果没有this,call就没有任何意义了,和普通的执行函数概念一样;
- call() 在执行函数时,函数的参数从第二位开始依次写入;
- 如果使用call() 或者apply() ,第一个参数为空或者是null,就意味着将函数中this 重定向到 window;
语法:方法名.call(对象)
function fn(_a,_b){
this.a=_a;
this.b=_b;
}
fn();//等同于
fn.call();//立即执行函数,this指向window
let obj={};
fn.call(obj);//立即执行函数,且this指向obj
fn(3,4);//等同于
fn.call(obj,3,4);
apply()
- apply() 和 call() 一样都是改变this的指向为第一个参数对象;
- apply() 的参数只有2个,第一个参数为函数中 this 的指向,第二个参数是一个数组,这个数组就是函数的参数;
- 如果使用call() 或者apply() ,第一个参数为空或者是null,就意味着将函数中this 重定向到 window;
function fn(_a,_b){
this.a=_a;
this.b=_b;
}
fn();//等同于
fn.call();//立即执行函数,this指向window
let obj={};
fn.apply(obj);//立即执行函数,且this指向obj
fn(3,4);//等同于
fn.apply(obj,[3,4]);
案例:
var obj={
abc:function(_a,_b){
this.a=_a;
this.b=_b;
}
}
obj.abc(3,5);//obj对象的a属性变为3,b属性变为5
var o1={};
obj.abc.call(o1,3,5);//将abc函数中的this指向o1
console.log(obj);//{a: 3, b: 5, abc: fn()}
console.log(o1);//{a: 3, b: 5}
如果使用call() 或者apply() ,第一个参数是null,就意味着将函数中this重定向到window;
var obj={
abc:function(_a,_b){
this.a=_a;
this.b=_b;
}
}
obj.abc.call(null,3,4);
//等同于
obj.abc.call(window,3,4);
console.log(obj);//{abc:fn()}
console.log(a,b);//3,4
案例:返回数组中最大的数值
因为Math.max 没有this,null,传入以后并没有任何作用,目的是传参时传入的是数组。
var Math={
max:function(){
if(arguments.length===0) return;
var max=arguments[0];
if(arguments.length===1) return max;
for(var i=1;i<arguments.length;i++){
max=max>arguments[i] ? max : arguments[i];
}
return max;
}
}
var arr=[1,3,5,6];
var max=Math.max.apply(null,arr);
console.log(max);//6
案例:将一个元素集合转为数组
<div></div>
<div></div>
<div></div>
<div></div>
<script>
var divs=document.getElementsByTagName("div");
console.log(divs);//HTMLCollection(5) [div, div, div, div, div]
var arr=Array.prototype.slice.call(divs);
//var arr=[].slice.call(divs);//跟上面的写法意思一样
console.log(arr);//[div, div, div, div, div]
</script>
Array.prototype.slice.call() 实现原理:
class Array1{
constructor(){
}
// 属性和方法 动态属性,实例属性,该类的原型属性,实例化的原型链属性
// 动态属性 其实相对static 静态来说的
// 实例属性,该属性是通过new 构造函数来实例化对象以后调用的
// 该类的原型属性 站在类的立场上,类.prototype.属性 ES5中的方式
// 实例化的原型链属性 站在实例对象的的立场上,ES5中,对象中原型链上的方法和属性
slice(start,end){
// ES6中实例方法中的this就是该类实例化的对象
start=Number(start);
end=Number(end);
if(isNaN(start)) start=0;
if(isNaN(end)) end=this.length;
if(start<0) start=this.length+start;
if(end<0) end=this.length+end;
var arr=[];
for(var i=start;i<end;i++){
arr.push(this[i]);
}
return arr;
}
}
Array1.prototype.slice();//this指的是Array1
var arr=new Array1();
arr.slice();//this指的是实例化出来的arr,它的__proto__是Array1
arr.slice.call(divs);
Array1.prototype.slice.call(divs);
原型链 原型
- 对象的原型链和构造函数(类)的原型是同一个引用对象;
- 原型链是针对对象的叫法,只有对象才有__proto__;
- 原型是构造函数(类)的叫法,只有构造函数(类)才有prototype;
- 实例化构造函数(原型)后,可以通过实例调用该构造函数(原型)的方法,会自动拥有自身原型的属性和方法;
class Box{
static a=3;
b=4;
constructor(){
}
play(){
console.log(this);//打印结果如下图
}
static once(){
}
}
Box.once();//静态方法
var b=new Box();
b.play();
Box.prototype.play();
可以看出 b.__proto__ === Box.prototype
//如果在对象的对象属性中没有找到,则会去原型链上找到里他最近的这个属性,
b.play()===b.__proto__.play();
Box.prototype.play()===b.play();
//得出如下结论
b.__proto__===Box.prototype
bind()
当需要在回调函数中重新执行回调函数中的this,就需要是用bind来指向对象
function fn1(fn){
fn(3);
}
function fn2(_a){
this.a=_a;
}
// 当需要在回调函数中重新执行回调函数中的this,就需要是用bind来指向对象
var obj={};
// 把fn2函数中的this指向obj,并且返回这个被指向this后新的函数
fn1(fn2.bind(obj));
console.log(obj);//{a:3}
// 这里创建了一个新的函数,return了一个新的函数
var fns=fn2.bind(obj);
console.log(fns===fn2);//false
bind()的实现原理:
function fn1(fn){
fn(3);
}
function fn2(_a){
this.a=_a;
}
function bind(fn,obj){
var fn1=function(){
fn.apply(obj,Array.from(arguments));
}
return fn1;
}
var obj={};
var fns=bind(fn2,obj);
fn1(fns);
console.log(obj);//{a:3}
案例:匿名回调函数中改变this的指向
var obj={
b:2,
a:function(){
setTimeout(function(){
console.log(this.b);//this指向window
},100)
setTimeout((function(){
console.log(this.b);//this指向obj
}).bind(this),100)
this.bindHandler=this.clickHandler.bind(this)
//监听事件
document.addEventListener("click",this.bindHandler);
},
clickHandler:function(e){
console.log(this.b);//this指向Obj
//解除事件监听
document.removeEventListener("click",this.bindHandler);
}
}
obj.a();
call() apply() bind() 的区别
- 它们在功能上是没有区别的,都是改变 this 的指向,区别主要是在于方法的实现形式和参数传递上的不同。 call 和 apply 方法都是在调用之后立即执行的。而bind 调用之后是返回原函数,需要再调用一次才能执行。
- 写法不同:
- 函数.call(对象,arg1,arg2…)
- 函数.apply(对象,[arg1,arg2…])
- var fn = 函数.bind(对象,arg1,arg2…)