前段时间有一场电话面试,记得里面有个内容,问我http相关的东西,我内心暗爽,我把http图解看了两遍了,里面的知识点啥的,基本都作了提炼和记录,http?那还不是so easy?javascript
balabala讲了一堆,从http到https到http2,还补充了点http3的东西,巴拉巴拉讲了一堆,信心满满.谁知道一个问题就问住我了.java
面试官: http2实现了多路复用,http1.x为何不能多路复用?
我: 我说由于http1.x要按照顺序来.
面试官: 没错,可是为何http1.x要按照顺序来?
我: 唔...这个不知道..
面试官: HTTP/1.1是基于文本分割解析的协议,也没有序号,若是多路复用会致使顺序错乱,http2则用帧的方式,等于切成一块块,每一块都有对应的序号,因此能够实现多路复用.es6
倒不是说这个问题怎么样怎么样,而是这个问题让我意识到学习还不够深刻,应该作到知其然并知其因此然.多思考多总结多提问,而后昨天恰好看到这样的一道面试题就稍微总结一下面试
apply,call,bind都是js给函数内置的一些api,调用他们能够为函数指定this的执行,同时也能够传参.api
//apply
func.apply(thisArg, [argsArray])
//call
fun.call(thisArg, arg1, arg2, ...)
//bind
const newFun = fun.bind(thisArg, arg1, arg2, ...)
newFun()
复制代码
apply和call就是传参不同,可是两个都是会在调用的时候同时执行调用的函数,可是bind则会返回一个绑定了this的函数.数组
咱们还须要知道一个事情,就是this的指向.bash
this的指向是在函数调用的时候肯定下来的,this的指向大体能够分为五种.app
默认绑定通常发生在回调函数,函数直接调用;函数
function test() {
//严格模式下是undefined
//非严格模式下是window
console.log(this);
}
setTimeout(function () {
//setTimeout的比较特殊
//严格模式和非严格模式下都是window
console.log(this);
});
arr.forEach(function () {
//严格模式下是undefined
//非严格模式下是window
console.log(this);
});
复制代码
这个通俗点用一句话归纳就是谁调用就是指向谁学习
const obj = {
name:'joy',
getName(){
console.log(this); //obj
console.log(this.name); //joy
}
};
obj.getName();
复制代码
const obj1 = {
name: 'joy',
getName() {
console.log(this);
console.log(this.name);
}
};
const obj2 = {
name: 'sam'
};
obj1.getName.call(obj2); //obj2 sam
obj1.getName.apply(obj2); //obj2 sam
const fn = obj1.getName.bind(obj2);
fn();//obj2 sam
复制代码
function Vehicle() {
this.a = 2
console.log(this);
}
new Vehicle(); //this指向Vehicle这个new出来的对象
复制代码
es6的箭头函数比较特殊,箭头函数this为父做用域的this,不是调用时的this.要知道前四种方式,都是调用时肯定,也就是动态的,而箭头函数的this指向是静态的,声明的时候就肯定了下来.比较符合js的词法做用域吧
window.name = 'win';
const obj = {
name: 'joy',
age: 12,
getName: () => {
console.log(this); //其父做用域this是window,因此就是window
console.log(this.name); //win
},
getAge: function () {
//经过obj.getAge调用,这里面this是指向obj
setTimeout(() => {
//因此这里this也是指向obj 因此结果是12
console.log(this.age);
});
}
};
obj.getName();
obj.getAge();
复制代码
既然有5种this的绑定方式,那么确定有优先级的前后
箭头函数 -> new绑定 -> 显示绑定call/bind/apply -> 隐式绑定 -> 默认绑定
这里直接给出告终论,有兴趣的小伙伴们能够本身去验证一下
先来实现apply吧
Function.prototype.myApply = function (context, args) {
}
复制代码
Function.prototype.myApply = function (context, args) {
//这里默认不传就是给window,也能够用es6给参数设置默认参数
context = context || window
args = args ? args : []
}
复制代码
Function.prototype.myApply = function (context, args) {
//这里默认不传就是给window,也能够用es6给参数设置默认参数
context = context || window
args = args ? args : []
//给context新增一个独一无二的属性以避免覆盖原有属性
const key = Symbol()
context[key] = this
//经过隐式绑定的方式调用函数
context[key](...args)
}
复制代码
Function.prototype.myApply = function (context, args) {
//这里默认不传就是给window,也能够用es6给参数设置默认参数
context = context || window
args = args ? args : []
//给context新增一个独一无二的属性以避免覆盖原有属性
const key = Symbol()
context[key] = this
//经过隐式绑定的方式调用函数
const result = context[key](...args)
//删除添加的属性
delete context[key]
//返回函数调用的返回值
return result
}
复制代码
这样一个简单的apply就实现了,可能会有一些边界问题和错误判断须要完善,这里就不作继续优化了
既然apply实现了,那么call一样也很是简单了,主要就是传参不同
这里就直接上代码吧
//传递参数从一个数组变成逐个传参了,不用...扩展运算符的也能够用arguments代替
Function.prototype.myCall = function (context, ...args) {
//这里默认不传就是给window,也能够用es6给参数设置默认参数
context = context || window
args = args ? args : []
//给context新增一个独一无二的属性以避免覆盖原有属性
const key = Symbol()
context[key] = this
//经过隐式绑定的方式调用函数
const result = context[key](...args)
//删除添加的属性
delete context[key]
//返回函数调用的返回值
return result
}
复制代码
bind和apply的区别在于,bind是返回一个绑定好的函数,apply是直接调用.其实想想实现也很简单,就是返回一个函数,里面执行了apply上述的操做而已.不过有一个须要判断的点,由于返回新的函数,要考虑到使用new去调用,而且new的优先级比较高,因此须要判断new的调用,还有一个特色就是bind调用的时候能够传参,调用以后生成的新的函数也能够传参,效果是同样的,因此这一块也要作处理 由于上面已经实现了apply,这里就借用一下,实际上不借用就是把代码copy过来
Function.prototype.myBind = function (context, ...args) {
const fn = this
args = args ? args : []
return function newFn(...newFnArgs) {
if (this instanceof newFn) {
return new fn(...args, ...newFnArgs)
}
return fn.apply(context, [...args,...newFnArgs])
}
}
复制代码
以上全部实现能够再加点判断啊,例如调用的不是function就返回或者抛出错误啊之类的.我这里就不处理了
以上就是apply,call,bind的实现了
一直在求学的道路上迷迷糊糊,磕磕碰碰,但愿能变得愈来愈棒,早日晋升大牛.多思考,多总结,多练习.养成终身学习的习惯.加油吧