调用函数时,隐式的函数参数 this 和 arguments 会被静默的传递给函数
this
表示调用函数的上下文对象
arguments
表示函数调用过程中传递的所有参数。通过 arguments 参数可以访问 函数调用过程中传递的实际参数。
函数调用的方式 对 函数的隐式参数有很大的影响
4.1 隐式的函数参数
arguments 和 this
arguments 参数
arguments 参数是 传递给函数的所有参数的集合
//arguments 是一个类数组对象
//length 属性 表示实参的确切个数
arguments[2]
function whatever(a,b,c){
arguments.length //实际传入的参数个数
}
//arguments 对象是函数参数的别名 所以改变了 arguments 对象的值,同时也会影响到对应的函数参数
复制代码
在严格模式下,不能改变 arguments[index] 的值。
"use strict"
function infiltrate(person){
arguments[0] = 'ninja'; //严格模式下 报错
}
复制代码
this 参数:函数上下文
调用函数时,除了显示提供的参数外,this 参数也会默认的传递给函数。 => 函数上下文
this
参数的指向 不仅是由 定义函数的方式 和 位置决定的,还受到 函数调用方式的影响。
4.2 函数调用
函数调用的4种方式
//1.作为一个函数直接调用
function skulk(name){}
shulk('Hattori');
//2.作为一个方法,关联到对象上调用
var ninja = {
shulk: function(){}
};
ninja.shulk('Hattori');
//3.作为构造函数调用
function Ninja(name){}
ninja = new Ninja('Hattori');
//4.通过apply 和 call调用
skulk.apply(ninja,['Hattori']);
skulk.call(ninja,'Hattori');
复制代码
作为函数直接调用
函数上下文 this 有两种可能性:
1.在非严格模式下,this是全局上下文(window对象)
2.在严格模式下,this是undefined
//函数定义 作为函数调用
function ninja(){};
ninja();
//函数表达式 作为函数调用
var samurai = function(){};
samurai();
(function(){})(); //立即调用的函数表达式,作为函数被调用
复制代码
//非严格模式下的函数调用
function ninja(){
return this; //window
}
//严格模式下的函数调用
function ninja(){
"use strict";
return this; //undefined
}
复制代码
作为方法被调用
当一个函数被赋值给一个对象的属性,并且通过对象属性引用的方式调用函数,函数会被作为 对象的方法 调用
当函数作为某个对象的方法被调用时,该对象会成为函数的上下文,并且在函数内部可以通过参数(this)访问到
var ninja = {};
ninja.skulk = function(){};
ninja.skulk();
复制代码
function whatsMyContext(){
return this;
}
whatsMyContext(); //当作函数调用 this == window
var getMyThis = whatsMyContext();
getMyThis(); //创建原函数的引用 当作函数调用 this == window
var ninja1 = {
getMyThis: whatsMyContext
}
ninja1.getMyThis == ninja1 //this 返回函数上下文 == 该方法所在的对象
复制代码
作为构造函数调用
//通过构造函数的方式调用,需要在函数调用之前使用关键字new
new MyObject();
function Ninja(){
this.skulk = function(){
return this;
}
}
var ninja1 = new Ninja();
ninja1.skulk() === ninjia1 //true
复制代码
当用 new
调用构造函数时,触发以下几个动作
- 创建一个新的空对象
- 该对象作为 this 参数传递给构造函数,从而成为构造函数的函数上下文
- 新构造的对象作为 new 运算符的返回值
构造函数的目的:创建一个新对象,并进行初始化设置,然后将其作为构造函数的返回值。
构造函数的返回值
function Ninja(){
this.skulk = function(){
return true;
};
return 1; //返回一个基本数据类型
}
Ninja() //返回值为1
//返回值1被忽略了,一个新的初始化对象返回
var ninja = new Ninja();
typeof ninja === "object" //true
typeof ninja.skulk === "function" //true
var puppet = {rules:false};
function Ninja(){
this.skulk = function(){
return true;
}
return puppet; //返回一个新对象
}
复制代码
- 如果构造函数返回了一个对象,那么该对象作为整个表达式的值返回,传入构造函数的 this 被丢弃
- 如果构造函数返回了 非对象类型,则忽略返回值,返回新创建的对象
apply/call方法调用
不同类型函数调用之间的区别:最终作为 函数上下文(this) 传递给 执行函数的对象不同。
直接函数:window undefined
方法:方法所在的对象
构造函数:新创建的对象实例
==每个函数都存在这两个方法==
改变函数的上下文,显示指定函数的上下文对象
事件回调函数的上下文 是 触发事件的对象 => 调用的位置
function Button(){
this.clicked = false;
this.click = function(){
this.clicked = true;
assert(button.clicked,"The button has been clicked");
}
}
var button = new Button();
var elem = document.getElementById("test");
elem.addEventListener("click",button.click);
复制代码
//apply和call方法来设置函数上下文
function juggle(){
var result = 0;
for (var n=0;n<arguments.length;n++){
result += arguments[n];
}
this.result = result;
}
var ninja1 = {};
var ninja2 = {};
juggle.apply(ninja1,[1,2,3,4]);
juggle.call(ninja2,5,6,7,8);
ninja1.result == 10
ninja2.result == 26
复制代码
强制指定回调函数的函数上下文
forEach 遍历函数将每个元素传给回调函数,将当前元素作为回调函数的上下文
function forEach(list,callback){
for(var n=0;n<list.length;n++){
callback.call(list[n],n); //当前元素作为函数上下文,循环索引作为回调函数的参数
}
}
var weapons = [{type:'abc'},{type:'bcd'},{type:'efg'}];
forEach(weapons,function(index){
this === weapons[index] //list[n]
})
复制代码
4.3 解决函数上下文的问题
上下文不一致可以考虑以下几种办法:
1.call 和 apply 显示指定 this 上下文
2.箭头函数
3.bind 方法
箭头函数绕过函数上下文
箭头函数没有单独的this值.
箭头函数的 this 与声明所在的上下文相同
//箭头函数自身不含上下文,从定义时所在的函数继承上下文
//调用箭头函数时,不会隐式传入this参数,而是从定义时的函数继承上下文
function Button(){
this.clicked = false;
this.click = () => {
this.clicked = true;
button.clicked == true; //button == this
}
}
var button = new Button();
var elem = document.getElementById("test");
elem.addEventListener("click",button.click);
复制代码
箭头函数与对象字面量
只有一个按钮,不需要使用构造函数。直接使用对象字面量。单例模式
//箭头函数与对象字面量
//对象字面量在全局代码中定义,所以this与全局代码的this相同
var button = {
clicked: false,
click: () => { //箭头函数在创建时确定了this的指向
this.clicked = true;
button.clicked; //false
this === window; //true
window.clicked; //true
}
}
var elem = document.getElementById("test");
elem.addEventListener("click",button.click);
复制代码
bind 方法
函数可以访问 bind 方法创建新函数,创建的的新函数与 **原始函数的函数体 **相同,新函数被绑定到指定的对象上,this 被设置为对象本身
调用 bind 方法不会修改 原始函数,而是创建了一个全新的函数
var button = {
clicked : false,
click : function(){
this.clicked = true;
button.clicked; //true
}
}
var elem = document.getElementById("test");
elem.addEventListener("click",button.click.bind(button));
//bind方法会新创建一个全新的函数
var boundFunction = button.click.bind(button);
boundFunction != button.click //true
复制代码
4.4 总结
-
当调用函数时,除了传入在函数定义中显式声明的参数之外,同时还传入两个隐式参数:arguments 与 this。
-
arguments 参数是传入函数的所有参数的集合。具有 length 属性,表示传入参数的个数,通过 arguments 参数还可获取那些与函数形参不匹配的参数。 在非严格模式下,arguments 对象是函数参数的别名,修改 arguments 对象 会修改函数实参,可以通过严格模式避免修改函数实参。
-
this 表示函数上下文,即与函数调用相关联的对象。函数的定义方式和调用 方式决定了 this 的取值。
-
-
函数的调用方式有 4 种。
-
作为函数调用:skulk()。
-
作为方法调用:ninja.skulk()。
-
作为构造函数调用:new Ninja()。
-
通过 apply 与 call 方法调用:skulk.apply(ninja)或 skulk.call(ninja)。
-
-
函数的调用方式影响 this 的取值。
-
如果作为函数调用,在非严格模式下,this 指向全局 window 对象;在严格 模式下,this 指向 undefined。
-
作为方法调用,this 通常指向调用的对像。
-
作为构造函数调用,this 指向新创建的对象。
-
通过 call 或 apply 调用,this 指向 call 或 apply 的第一个参数。
-
-
箭头函数没有单独的 this 值,this 在箭头函数创建时确定。
-
所有函数均可使用 bind 方法,创建新函数,并绑定到 bind 方法传入的参数上。被绑定的函数与原始函数具有一致的行为。