call,apply都是为了改编某个函数运行时的上下文context而存在,就是为了改变内部的this指向
1 .js的一大特点就是,函数存在定义时上下文,运行时上下文,以及上下文是可以改变的这样的概念
var name="小王"
var age=17
var obj={
name:"小李",
age:19,
objName:this.name,
// 这里的this指的是全局的this
objAge:this.age,
myFunc:function(){
console.log(this.name,this.age)
// 这里的this是函数作用域的this,也就是obj
}
}
console.log(obj.objName,obj.objAge)
obj.myFunc()
1 .全局执行环境,严格模式下this指向undefined,非严格模式,指向window,global,self
2 .作为对象方法的调用,假设函数作为一个方法被定于i在对象中,那么this指向最后调用他的这个对象
call:函数没有使用括号之前使用它可以改变里面函数的this指向(可以有多个参数,只有第一个表示要改变的对象)
0 .fun.call(this.obj,arg1,arg2,arg3)
1 .第一个参数是调用函数时this的指向,随后的参数作为函数的参数并调用,也就是fun(arg1,arg2)
let obj={
a:10
}
function test(a,b){
console.log(a)
console.log(b)
console.log(this.a)
}
test()
//三个值都是undefined
test.call(obj,11,12)
//11,12,10
1 .当一个obj里面没有某个方法,但是其他的某个对象有这个方法,我们就可以用call来使用其他对象的方法来操作本对象
1 .function cat(){
}
cat.prototype={
"food":"fish",
say:function(){
c(this.food)
}
}
const dog={
food:'bone'
}
const c1=new cat();
c1.say.apply(dog)
2 .将参数和函数进行分离
function changeStyle(attr,value){
this.style[attr]=value;
}
const box=document.getElementById('box')
window.changeStyle.call(box,'height','200px')
window.changeStyle.apply(box,['height','200px'])
//区别在于传递函数的方式
apply:(可以使用多个参数,但是多于的参数必须以数组的形式传入)
- 如果以上两个方法的第一个参数是null的话,那么指向window。
2 .func.apply(thisArg, [argsArray])
3 .thisArg:可选的,func函数运行时使用的this值。
4 .利用apply可以使用一些本来需要写成遍历数组变量才可以使用的内建函数
let number=[1,2,3,4,5,6,7]
Math.max.apply(null,number)
ES6中更为简单
Math.max(...number)
let arr=[1,2,3,4,5]
console.log(Math.max(arr))
//直接使用,返会NaN,在Math.max的参数里面,如果有一个参数不能直接转化为数字,那么就会返回NaN
let arr=[1,2,3,4,5]
// console.log(Math.max(arr))
console.log(Math.max.call(null,1,2,3,4,5))
console.log(Math.max.apply(null,arr))
console.log(Math.max(...arr))
//这三种都是返回一个结果,都是5
//大量的数组需要将参数切块之后循环传入目标方法。
function minofArray(arr){
let min=Infinity
let quantum=32768
for(let i=0,len=arr.len;i<len;i+=quantum){
let submin=Math.min.apply(null,arr.slice(i,Math.min(i+quantum,len)))
min=Math.min(submin,min)
}
return min
}
5 .apply和call只有一个区别,就是传入的参数的个数不同,前者可以传入很多个,后者只能传入两个参数,而且第二个参数为调用函数时的参数构成的数组
6 .fun.apply(this.obj,[args])
7 .当不给函数传参数的时候,他们俩其实一样,需要传参数的时候应该注意他的参数转换成数组形式
function displayHobbies(...hobbies) {
console.log(`${this.name} likes ${hobbies.join(', ')}.`);
}
// 下面两个等价
displayHobbies.call({ name: 'Bob' }, 'swimming', 'basketball', 'anime'); // => // => Bob likes swimming, basketball, anime.
displayHobbies.apply({ name: 'Bob' }, ['swimming', 'basketball', 'anime']);
bind:
不同1:bind返回一个修改过的函数,必须再次调用才会起作用,前两个都是使用完直接就输出的。
不同2:他传入的参数是按照实参,形参结果表示的、
1 .bind多次绑定一个函数,后续的绑定不会生效,始终指向第一次被绑定的函数
2 .bind绑定之后的函数,不能再使用call,apply再次改变函数的指向
3 .bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数
函数的使用场景
1 .作为某个对象的方法
1 .函数中的this往往指向的都是调用他的对象
2 .作为一个独立的函数,一般不在里面使用this
3 .es6之前用来展开数组调用,es6之后使用...操作符,多参函数转换为单个数组参数调用
Array.prototype.push.call(arr1,arr2)
//es6
[...arr1,...arr2]
arr1.push(...arr2)
4 .将类数组转化为数组
1 .Array,prototype.slice.call(类数组)
es6转换
const arr=Array.from(类数组)
或者
[...类数组]
5 .实现组合继承
类.call(this,类)
四条规则
1 .默认绑定,没有其他装饰bind,apply,call,非严格模式下指向全局对象,严格模式下定义指向undefined
function foo(){
console.log(this.a)
}
var a=2
foo()
//2
2 .隐式绑定:调用位置是否有上下文对象,或者是被某个对象拥有或者包含,那么隐式规定则会把函数调用中的this绑定到这个上下文对象,而且,对象属性链只有上一层或者最后一层在调用位置中起作用
function foo(){
console.log(this.a)
}
var a="123"
var obj={
// a:2,
//a被注释的话,返回的是undefined
//a没有被注释,返回的是2
foo:foo,
}
obj.foo()
3 .显示绑定,通过在函数上运行call,apply来显示的绑定this
function foo(){
console.log(this.a)
}
var obj={
a:1
}
foo.call(obj)
//1
foo()
//undefined
4 .new 绑定。new调用函数会创建一个全新的对象,兵将这个对象绑定到函数调用的this。
自己实现call,apply,bind
Function.prototype.call=function(context,...args){
context=context||window
//这里要针对不同的类型thisArg分别处理
//null,undefined,不传,this将会指向全局对象
//原始值将被转为对应的包装对象,如call(1),this将指向Number
const fnSymbol=Symbol('fn')
context[fnSymbol]=this
//这里的this就是func.call()中的func
const result = context[fnSymbol](...args)
//通过这里调用func来改变this指向的作用
delete context[fnSymbol]
return result
}
Function.prototype.apply=function(context,argsArr){
context=context||window
const fnSymbol=Symbol('fn')
context[fnSymbol]=this
context[fnSymbol](...argsArr)
delete context[fnSymbol]
}
Function.prototype.myBind = function (thisArg, ...args) {
const func = this;
// bind 返回的是一个新函数,如果使用 new 调用了被绑定后的函数,其中的 this 即是 new 最后返回的实例对象,也就是 target
const boundFunc = function (...otherArgs) {
// 当 new.target 为 func,不为空时,绑定 this,而不是 thisArg
return func.call(new.target ? this : thisArg, ...args, ...otherArgs)
};
boundFunc.prototype = Object.create(func.prototype);
boundFunc.prototype.constructor = boundFunc;
Object.defineProperties(boundFunc, {
name: {
value: `bound ${func.name}`
},
length: {
value: func.length
}
});
//实现原函数的bind后的bound函数的name和length
return boundFunc;
}
改变this指向的方法
1 .箭头函数
2 .内部缓存this
a = 10
obj = {
a: 20,
f() {
const _this = this
setTimeout(function() {
console.log(_this.a, this.a)
}, 0)
}
}
obj.f() // _this.a 指向 20 this.a 则指向 10
3 .apply
4 .call
5 .bind
6 .new 操作符
new 操作符实际上就是生成一个新的对象,这个对象就是原来对象的实例。因为箭头函数没有this,所以箭头函数不能作为构造函数,构造函数通过new操作符改变了this的指向
function Persion(name){this.name=name}
this.name表示新创建的实例拥有一个name属性,当调用new的时候,构造函数中的this就绑定在了实例上面
self改变指向的操作
var foo = {
bar : 1,
eventBind: function(){
var _this = this;
$('.someClass').on('click',function(event) {
/* Act on the event */
console.log(_this.bar); //1
});
}
}
性能问题
1 .参数小于3个的时候,call强,大于3个的时候apply,统一使用reflect