bind,call,apply
bind,call,apply作用都可以改变this的指向。对函数调用bind,call,apply,第一个参数是希望this指向的对象,后面就是传递的参数。
用法如下:
let dog={
name:'dog',
sayname(num1,num2){
console.log('name is',this.name)
console.log('sum is',num1+num2)
}
}
let cat={
name:'cat'
}
dog.sayname(1,2)
dog.sayname.call(cat,1,2)
dog.sayname.apply(cat,[1,2])
//bind一种方式
let fn=dog.sayname.bind(cat,1,3)
fn()
//bind另一种方式,柯里化
let fn=dog.sayname.bind(cat,1)
fn(3)
区别:
- call和apply区别就是传递参数的方式,call参数是一个一个传递的,apply传递的参数以数组的形式。
- bind和call,apply的区别,bind调用后返回一个值,不是立即执行去函数,所以需要一个函数接收返回值,然后调用函数。call和apply调用后会立即执行函数。 注意bind的一个特点:可以实现柯里化,bind参数可以部分传递,调用新函数在传递剩余参数。
原生js
代码
我们自己写一个call,apply,bind
原始js实现call
Function.prototype.mycall = function (ctx, ...args) {
ctx = ctx || globalThis //如果没有指定对象,就需要指定全局对象
//this指向调用mycall的对象,this()就是执行Mycall方法,我们需要去让this指向ctx
// 创建一个唯一的属性名,用于存储我们的函数
const fn = Symbol();
ctx[fn] = this //this放到指向对象的属性里面,这样this就指向了ctx
// 调用ctx对象上的函数,并传递参数
const result = ctx[fn](...args)
delete ctx[fn]
// 函数需要返回值
return result
}
function add(x, y) {
console.log(this, x, y)
return x + y
}
// 但是我们的ctx里面有一个参数fn
console.log(add.mycall({}, 1, 2, 3))
原始js实现apply
Function.prototype.myapply=function(ctx,args){
ctx=ctx || globalThis
const fn=Symbol()
ctx[fn]=this
apply必须入数组,所以需要判断,
if(!Array.isArray(args){
throw new TypeError('参数错误')
}
const result=ctx[fn](...(args || [] )
delete ctx[fn]
return result
}
console.log(myapply({},[1,2,3])
原始js实现bind
function fn(...args) {
console.log('args', ...args)
console.log('this', this)
return 11
}
Function.prototype.mybind = function (ctx, ...args) {
// 对类数组(函数的参数argument)切割获取得到数组
const arg1 = Array.prototype.slice.call(args, 1)
const fn = this
// bind返回函数调用,调用的函数就是newfn,实际上就是fn,也就是this
// 参数是两次都有,需要我们合并起来
return function A(...args) {
// 类数组转化为数组
let arg2 = Array.prototype.slice.call(args)
let arg = [...arg1, ...arg2]
// 需要设置return的返回值,但是我们需要判断,知道他第二次使用函数是
// 通过判断原型得知他是通过new还是什么方法调用的
if (Object.getPrototypeOf(this)===A.prototype) {
// 这里就不用修改this的指向了
return fn(...arg)
}
else {
return fn.apply('ctx', arg)
}
}
}
let newfn = fn.mybind({ 'name': 'jack' }, 1, 2)
// 1.直接使用,这里this指向就是我们bind指定的
let news = newfn(3, 4)
// 2.通过new一个实例,this指向的就是实际调用bind的函数fn
console.log(news)
补充:柯里化。
简单来说就是将函数转化为接收部分参数的函数。也就是函数里面返回函数。
具体可以看这个视频:JavaScript函数柯里化 - Web前端工程师面试题讲解_哔哩哔哩_bilibili
作用一:针对函数传递参数的优化
//输入网址的时候,https/http都是重复的,所以我们使用函数柯里化
let url=function(protocol){
return function(hostname,pathname){
return protocol+hostname+pathname
}
}
let urlall=url('https://')
let url1=urlall('www.baidu.com','/index')
let url2=urlall('www.xinlang.com','/login')
console.log(url1,url2)
作用二:兼容性的检测。比如提前判断浏览器是否支持相应的方法。
//attachEventh(IE)和addEventListner(主流浏览器方法)
// 提供跨浏览器的方法来添加事件监听器,确保无论在哪种浏览器环境下,开发者都能以一致的方式工作。
// 使用立即执行函数
const whichevent=(function(){
if(window.addEventListener){
return function(element,type,listener,usecapture){
element.addEventListener(type,function(e){
listener.call(element,e)
},usecapture)
}
}else if(window.attachEvent){
return function(element,type,handler){
element.attachEvent('on'+type,function(e){
handler.call(element,e)
})
}
}
})()
//如何使用
whichevent(document.getElementById('myButton'), 'click', function(event) {
alert('Button was clicked!');
}, false);
作用三:延迟执行,需要时才执行某个操作或计算,而不是在程序一开始就立即执行。
题目
add(1)(2)(3)=6
add(1,2,3)(4)=10
add(1)(2)(3)(4)(5)=15
//add() 没有去执行inner这个函数,返回return inner,
//add()() 去执行inner这个函数,返回return sum
function add(){
// arguments是一个对象,所以我需要把它转变为数组
// let args=Array.prototype.slice.call(arguments)
let args=[...arguments]
let inner=function(){
args.push(...arguments)
let sum=args.reduce(function(pre,cur){
return pre+cur
},0)
return sum
}
return inner
}
console.log(add(1,2,3,4)(2))
// 但是现在面临的问题就是不能是不能任意延迟输入参数,
// 每次增加一个(),就需要新增一个内部执行函数,太麻烦
// 我们的解决办法就是通过递归,内部函数返回内部函数,但是函数返回的内容怎么办?
// 可以通过 inner.toStrng控制台输出
function add(){
// arguments是一个对象,所以我需要把它转变为数组
let args=[...arguments]
let inner=function(){
args.push(...arguments)
return inner
}
inner.toString=function(){
return args.reduce(function(pre,cur){
return pre+cur
},0)
}
return inner
}
console.log(add(1,2,3,4)(22)(2).toString())
作用四:含有柯里化的箭头函数
const namelist1=[
{mid:'one',profession:'yi'},
{mid:'two',profession:'er'},
{mid:'three',profession:'san'},
{mid:'four',profession:'si'}
]
const namelist2=[
{adc:'jam',profession:'abc'},
{adc:'mack',profession:'abc'},
{adc:'tom',profession:'abc'},
]
// 输出数组对象中的abc和mid
console.log(namelist1.map(item=>item.mid))
console.log(namelist2.map(item=>item.adc))
// 如果对象的键值对不一样,我们柯里化箭头函数
const curfn=name=>element=>element[name]
const namemid=curfn('mid')
console.log(namelist1.map(namemid))
补充:立即执行函数IIFE
。
定义变量fn
接收立即执行函数的返回值,调用fn
函数,实际上是在调用return
返回的函数,因为内部函数被赋值给fn
。
function fn(){}
的意思就是声明语句,语句没有返回值,不可以使用括号调用。只有表达式才有返回值,如fn()
特点:1.不必为函数命名,避免污染全局变量。2.立即执行函数内部形成了单独的作用域,可以封装一些外部无法读取的私有变量。
//首先格式有两种
(function(){})()
(funcion(){}())
//方式一
(function () {
console.log(2321)
})();
//方式二
(function () {
console.log(1111)
}())
//fn是立即执行函数表达式,调用fn传递参数,实际上是在调用立即执行函数返回的函数。
//所以下面传参,可以实现两数相加,调用了立即执行函数返回的函数。
let fn=(function(){
return function(x,y){
return x+y
}
})()
console.log(fn(1,2))