一、先说下this是什么?
定义:this是函数内部的一个特殊对象,this引用的是函数执行的环境对象。
比如在浏览器中,全局调用一个函数的话,他的执行环境对象就是window,所以此刻this = window。看个例子
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //"red" 执行环境为全局, 所以this = window
o.sayColor = sayColor;
o.sayColor(); //"blue" 执行环境是o对象,所以this = o
👆上面例子中,sayColor()在全局执行,所以this.color 就是window.color 就是red。而o.sayColor 的执行环境是o对象,所以this=o,o.color就是blue。
函数内部还有另一个特殊对象arguments,是个类数组。
可以通过ES6的Array.from()或者ES5的[].slice.call(arguments)实现转成真正的数组。在👇下面bind的实现里有这个转换的具体应用
另外它有个属性callee,指向拥有arguments对象的函数,也就是正在执行的函数。常用arguments.callee来实现和函数名的解耦, 比如阶乘递归函数 阶乘递归的具体实现
二、改变this指针的5个方法
均以👆上面例子为例
1. 函数方法apply
函数内部的方法:apply(运行函数的作用域,参数数组)
sayColor.apply(this) // red
2. 函数方法call
与apply唯一的不同就是第二个参数是逐个参数传入的
sayColor.call(o) // blue
JS原文:每个函数都有2个非继承方法,apply和call,他们的用途都是在特定的作用域中调用函数,实际上等于设置函数体内的this对的值。apply和call的作用相同,只是接受参数的方式不同,call的第一个值和apply一样,第二个值apply接受一个数组或者arguments对象,call是参数必须逐个列举出来。他们的真正的强大的地方就是能够扩充函数赖以运行的作用域
3. ES5定义的bind函数
ES5定义的的方法,这个方法会创建一个函数的实例,其this值会被绑定传给bind()函数的值。
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
4. this赋值给闭包能访问到的变量中
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
} };
alert(object.getNameFunc()()); //"The Window"
按照this指向运行环境的对象来说,上面的this应该指向getNameFunc,但是由于
匿名函数执行环境具有全局性,所以this= window
所以 this.name 就是 The Window。所以要得到My Object,应该
var that = this;
return function(){
return that.name;
};
顺便看下匿名函数和闭包都是什么?
4.1 匿名函数:函数表达式的创建方式创建出来的函数
创建一个函数赋值给变量的创建方式就是一个匿名函数。
const s = function(){}这就是一个匿名函数,因为function关键字后面没有标识符
4.2 闭包:有权访问另一函数作用域内变量的函数
- 创建闭包的最常见方式:一个函数内创建另一个函数
- 副作用:闭包只能取得包含函数中任何变量的最后一个值
- 解决方案:通过创建另一个匿名函数强制让闭包的行为符合预期
eg: 点击页面的li列表弹出每个li的索引
var arr = document.getElementByTagName('ul')[0].querySelectorAll('li')
<!--错误案例❎-->
for(var i =0;i<arr.length;i++){
arr[i].onclick = function(){ //闭包只能取得包含函数最后一个值
console.log(i) //全都是3
}
}
<!--解决方案一:✅-->
for(var i =0;i<arr.length;i++){
arr[i].onclick = function(num){ //创建另一个匿名函数
return function(){
console.log(num) //0,1,2
}
}(i)
}
<!--解决方案二: ES6 的let ✅-->
for(let i =0;i<arr.length;i++){ // let 只在所在的命令块中有效
arr[i].onclick = function(){
console.log(i) //0,1,2
}
}
5. ES6 箭头函数
- 函数体内的this对象, 就是定义所在的对象,不是使用所在的对象
- 不可以当做构造函数,也就是不能new
- 不可以使用arguments对象,该对象在函数体内不存在,如果要用可以用reset代替
- 不可以使用yield命令,因此箭头函数不能作为generator函数
setTimeout(()=>{
console.log("箭头函数this是固定的")
})
三、自己实现一个bind函数[简单版本]
了解了函数的一些属性和方法以及改变this指针的一些方法,下面让我们来实践一下吧,自己实现一个简单的bind绑定函数吧!
使用方式sayColor.bind(o)();
思路如下
- 肯定在Function.prototype 上
- 可以改变context环境对象, 也就是说需要用到apply
- 实现柯里化,也就是传参后调用的时候也传入参数:需要合并所有的参数
var foo = {
color: 'red',
sayColor: function () {
console.log(this.color)
console.log(Array.from(arguments))
}
}
Function.prototype.mybind = function(context){
var that = this;
var args = [].slice.call(arguments,1) //第一个参数是环境变量
return function(){
var all_args = args.concat (Array.from(arguments))
that.apply(context,all_args)
}
}
foo.sayColor.mybind(foo,'参数1','参数2')('柯里式')
四、函数的其他属性
以上的arguments、this、apply、call都是函数的方法或者对象,除非之外函数还有以下三个属性
1.函数caller属性
- ES5 规范的函数对象的属性:caller。ES3中没有定义
- opera早起不支持,其他浏览器都支持
- 保存着 【调用当前函数的】的函数的引用
- 如果在全局作用域中调用当前函数,值为null
- 严格模式下:arguments.callee 会报错
- 严格模式下:不能为函数的caller属性赋值,否则会导致错误
- ES5还定义了arguments.caller。严格模式下报错,非严格下undefined 。定义这个属性只是为了区分函数的caller
以上变化都是为了加强语言的安全性,这样第三方代码就不能在相同的环境中窥视其他代码了。
function outer(){
inner();
}
function inner(){
alert(arguments.callee.caller); // 其实就是inner.caller
}
outer();
2. length:表示函数希望接受的命名参数个数
3. prototype:原型
- 是保存实例化方法的真正所在。
- 比如toString(),valueOf()都是在这上面。
- prototype属性不可枚举,因此for-in不能发现
在创建自定义引用类型,以及实现继承上,非常重要,这里暂时先不介绍了
五、总结
看上以上内容,可以回答下下面的面试题吗?
- 你知道闭包吗?
闭包就是有权访问另一函数作用域内变量的函数,最常见的创建方法就是函数内创建另一个函数,副作用是闭包只能取得包含函数中任何变量的最后一个值,比如for循环,解决方案是内部创建一个匿名函数并把参数穿进去或者let。
- 改变this指针的方法
- apply
- call
- 闭包内赋值外部作用域this
- bind函数
- ES6的箭头函数
- 自己实现一个bind函数: 看👆上面
- 写一下实现3!的代码