- 简单来说apply等方法的作用就是更改this的指向,让被绑定的对象可以调用别的对象的方法
let a = {
name : 'a',
sayName:function(){
console.log(this.name)
}
}
let b = {
name : 'b',
}
a.sayName.apply(b) //b
//我们可以看到b对象调用了本该属于a对象的方法
this指向
- 这里我们讨论的是浏览器中的this指向,Node中的this 与浏览器中的this不同,Node中的this指向module.exports
- 简单的我们可以将this的指向分为几种情况
没有被调用时
- this会默认指向全局对象
(function a(){
console.log(this)
})() //window
被调用时
- this会指向调用它所在函数的对象
let a = {
name : 'a',
sayName:function(){
console.log(this)
}
}
a.sayName(); //{ name: 'a', sayName: [Function: sayName] }指向对象a
箭头函数
在其中会有两种情况
- 父元素被调用,this会指向父元素的this指向
let a = {
name : 'a',
sayName:function(){
(()=>{
console.log(this)
})()
}
}
a.sayName(); //{ name: 'a', sayName: [Function: sayName] }指向对象a
- 元素没有被调用,this指向全局对象
let a = {
name : 'a',
sayName:()=>{
console.log(this)
}
}
a.sayName(); //window
注:
严格模式下,如果 this 没有被执行环境定义,那它将保持为 undefined
setTimeout除外,无论是否为严格模式,this都会指向全局对象
"use strict";
(function a(){
console.log(this)
})() //undefined
使用apply、call、bind改变this
- this会指向apply、call、bind绑定的对象
let a = {
name : 'a',
sayName:function(a){
console.log(this)
}
}
let b = {
name : 'b',
}
a.sayName.apply(b) //{ name: 'b' }指向对象b
使用new函数
- this会指向构造函数
let A = function(){
console.log(this)
}
let a = new A() //A
简单实现apply
- 首先,我们根据上面的写法可知,apply是一个有着两个参数的函数并且可以被所有函数调用
Function.prototype.newApply = function(obj,arg){}
- 第二步,假如没有参数,函数将绑定全局对象
obj = obj || window
- 第三步,当拥有第二个参数且第二个参数不为数组时,整体会报错
if(arg !== undefined){
if(!Array.isArray(arg) ){
throw new Error('it must be an Array');
}
for(let i = 0;i < arg.length;i++){ //将数组中转化为字符串
newArg.push (' arg[' + i + ']');
}
}
- 第四步,在绑定的对象上创建一个属性用以接收方法
let attr = Symbol(); //为了避免出现属性重复
obj.attr = this;
let result = eval('obj.attr('+newArg+')'); //运行函数并接受值,使用eval函数运行
- 第五步,删除创建的属性
delete obj.attr;
- 第六步,返回运行值
return result;
完整版
Function.prototype.newApply = function(obj,arg){
let newArg = [];
if(arg !== undefined){
if(!Array.isArray(arg) ){
throw new Error('it must be an Array');
}
for(let i = 0;i < arg.length;i++){
newArg.push (' arg[' + i + ']');
}
}
obj = obj || window;
obj.attr = Symbol();
obj.attr = this;
let result = eval('obj.attr('+newArg+')')
delete obj.attr;
return result;
}
简单实现call
- 与apply唯一的不同就是,apply接收数组,而call则是一个一个接收参数
Function.prototype.newCall = function(obj,arg){
let newArg = [];
for(let i = 1;i < arguments.length;i++){
newArg.push (' arguments[' + i + ']'); //相比于apply,可以直接使用arguments获取除第一位之外的参数
}
obj = obj || window;
obj.attr = Symbol();
obj.attr = this;
let result = eval('obj.attr('+newArg+')')
delete obj.attr;
return result;
}
简单实现bind
- 与apply、call区别最大的是,bind绑定的函数并不会直接被调用,所以bind最常被用于回调函数之中,既然他需要被调用,那么说明他返回一个函数
Function.prototype.newBind = function(obj,arg){ //这里的参数为bind方法的参数
let _this = this;
if(!(this instanceof Function)){ //可以进行简单的判断,因为bind只能被函数调用
throw new Error('it must be a Function to use it');
}
let newArg = Array.prototype.slice.call(arguments,1); //将伪数组转化为字符串
return function(newArgu){ //这里的参数为bind绑定后的函数参数
newArgu = Array.prototype.slice.call(arguments);
return _this.apply(obj,newArg.concat(newArgu)); //如果函数有值,则返回值
}
}
- 除此之外,bind还可以绑定构造函数,但众所周知,new操作符会改变this的指向,虽然MDN不推荐使用,但姑且考虑一下:)
- 我们可以将返回的函数提取出来,如果作为构造函数,它就相当于绑定后的构造函数,使用new方法后this指向实例,‘this instanceof funBind’返回true
- 如果只是普通函数,this则指向window,‘this instanceof funBind’返回false
let funBind = function(){
return _this.apply(this instanceof funBind ? this : obj,newArg.concat(newArgu)); //判断this指向
}
funBind.prototype = this.prototype; //获取绑定函数原型上所有的数据
完整版
Function.prototype.newBind = function(obj,arg){
let _this = this;
if(!(this instanceof Function)){
throw new Error('it must be a Function to use it');
}
let newArg = Array.prototype.slice.call(arguments,1);
let funBind = function(newArgu){
newArgu = Array.prototype.slice.call(arguments);
return _this.apply(this instanceof funBind ? this : obj,newArg.concat(newArgu));
}
funBind.prototype = this.prototype;
return funBind;
}
当然以上的写法只是一定程度上还原函数本身的作用,底层一定比这复杂得多,想要更加具体的了解请自行搜索
借鉴:网上查找 + 《JavaScript高级程序设计》 + MDN文档 + 自己的理解
如有错误,希望提出
希望大家都能早日拿到心仪的offer,加油,共勉