函数实际上是对象,每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。
由于函数是对象,因此函数名实际也是一个指向函数对象指针,不会与某个函数绑定。
一、函数的创建方法:
函数通常使用函数声明语法来创建函数,如下:
function sum(num1,num2){ // 函数声明
return num1 + num2;
}
也可以使用函数表达是创建函数,如下:
var sum = function(num1,num2){ // 函数表达式
return num1 + num2
}
还有一种定义函数的方式是使用Function构造函数。Functiongou构造函数可以接受任意数量的参数,但是最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数,如下:
var sum = new Function ("num1", "num2", "return num1 + num2"); // 不推荐这样创建函数,
**函数是对象,函数名是指针**
函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同,一个函数可能有多个名字,如下示例:
function sum(num1,num2){ // 声明创建函数
return num1 + num2;
}
console.log(sum(1,2));
var anot = sum; // 将 sum函数赋值给anot,此时anot和sum指向同一个函数
console.log(anot(10,20));
sum = null; // 将sum函数赋值成空
console.log(anot(40,90)); // 任然可以正常调用anot函数
二、函数没有重载(不能有同名函数在一起使用)
将函数名想像为指针,可以理解为什么js中函数没有重载的概念。
两个同名函数,后面函数会把前面的函数覆盖;
例如:
function fn1(num){
return num + 10;
};
function fn1(s){
return s + 60;
};
console.log(fn1(100))
三、函数声明和函数表达式
函数声明和函数表达式几乎是一样,但是本质上还是有区别的:
js解析器在执行环境中加载数据时,对函数声明和函数表达试并非是一视同仁。
以下是函数声明和函数表达式的区别:
解析器会率先读取函数声明,并使其在执行任何代码之前可以调用;
函数表达式必须等解析器执行到它所在的代码,在会真正被解释执行。
函数声明会将函数声明提升到顶部,函数表达式则不会。
直接上码:
// 函数声明代码:
console.log(sum(10, 10)); // 20
function sum(num1,num2){ // 函数声明,可以函数的前后都可以调用
return num1 + num2;
};
上面代码开始执行之前,解析器就会做函数声明提升的过程,读取并将函数声明添加到执行环境中。
对代码求值时,js引擎在第一遍会声明函数并将它们放到源代码树的顶部。
所以,即使声明函数的代码在调用它的代码后面,js引擎也能把函数声明提升到顶部。
下面的代码之所以会报错,原因在于函数位于一个初始化语句中,而不是一个函数声明。
在函数所在的语句之前,变量sum中不会保存有对函数的引用,而且由于第一行代码导致的错误,下面的代码也不会执行。
//函数表达式代码:
console.log(10,10); // Uncaught TypeError: sum is not a function
var sum = function(num1,num2){ // 函数表达式,此时如果在此函数前面调用会报错,在后面调用,正常执行
return num1 + num2
};
console.log(10,10); // 20
除了什么时候可以通过变量访问函数这一点区别之外,函数声明与函数表达式的语法其实是等价的。
四、作为值的函数
在js中函数名本身就是变量,所以函数也可以作为值来使用,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回,如下代码:
function call(someFun,someArg){ // 第一个参数是个函数,第二个参数是传递给第一个参数函数的一个值
return someFun(someArg)
}
function add10(num) {
return num +10;
}
var result = call(add10,10);
console.log(result) // 20
五、函数内部属性
在函数内部有两个特殊的对象:arguments和this对象。
1、arguments
arguments是个类数组对象,包含着传入函数中所有的参数,虽然arguments的主要用途是保存函数参数(实参),但这个对象还有一个名叫callee的属性,该属性时一个指针,指向拥有这个arguments对象的函数。
callee是arguments对象的一个属性返回一个正在被执行函数的引用,例如:
function a(x,y){
console.log(arguments.length); // 2
console.log(arguments.callee.length); // 2
console.log(argurmnts.callee); // 返回函数体
}
ECMAScript 5 也规范化了另一个函数对象的属性: caller 。除了 Opera 的早期版本不支持,其他浏览器都支持这个 ECMAScript 3 并没有定义的属性。这个属性中保存着调用当前函数的函数的引用,
如果是在全局作用域中调用当前函数,它的值为 null 。
2、this
函数内部另一个特殊对象是this,this引用的是函数执行的环境对象(当网页的全局作用域中调用函数时,this对象引用的就是window),说白了this就是指向函数的调用,谁调用函数this就指向谁,例如:
window.color = "red";
var o = {color:"blue"};
function sayColor(){
console.log(this.color); // red 这个this指的是window
}
sayColor();
o.sayColor = sayColor; // 把函数赋值给对象 o 此时this就指向是对象o
o.sayColor(); // blue
六、函数属性和方法
js中函数也是对象,是对象就有属性和方法。
每个函数都是两个属性:length和prototype。
length:表示函数参数的个数
function sayHi(){
alert("hi")
}
console.log(sayHi.length); // 0 函数的形参数是0个
function sayName(name){
alert(name)
}
console.log(sayName.length); // 1 函数的形参数是1个
function sum(num1,num2){
return num1 + num2;
}
console.log(sum.length); // 2 函数的形参数是2个
prototype:是保存所有引用类型实例方法的真正所在,例如toString()和valueOf()等方法实际上是保存在prototype名下,只不过是通过各自实例对象等访问。
每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法都是在特定的作用域中调用函数,实际上是等于改变函数的this对象的指向的(改变函数的指向)。
apply()方法接收两个参数:
一个是运行函数的作用域,另一个参数是数组。其中第二个参数可以是数组(Array)实例,也可是是arguments对象,例如:
function sum(num1,num2){
return num1 + num2
}
function callSum1(num1,num2){
return sum.apply(this,arguments); // // 第二个参数是个数组或者arguments
}
function callSum2(num1,num2){
return sum.apply(this,[num1,num2]); // 第二个参数是个数组或者arguments
}
console.log(callSum1(10,10)); // 20
console.log(callSum2(5,5)); // 10
在严格模式下,未指定环境对象而调用函数,则 this 值不会转型为 window。 除非明确把函数添加到某个对象或者调用 apply()或 call(),否则 this 值将是 undefined。
call()方法也是接收两个参数。
call()用法基本上和apply()方法一样,也是接收两个两个参数,第一个参数是运行在函数的作用域,和apply()方法的第一个参数一样,第二个参数必须逐个的列举出来。
function sum(num1,num2){
return num1 + num2;
}
function callSum3(num1,num2){
return sum.call(this,num1,num2); // call方法第二个参数必须明确的传入每一个参数,结果和apply一样
}
console.log(callSum3(30,30)); // 60
call()方法参数有4种情况:
(1)、不传值,或者传入null,undefined,函数中的this指向的都是window
(2)、传递另一个函数的名字,函数中的this指向这个函数的引用
(3)、传递基本类型的时候,函数this指向其对应的包装对象,如String,Number,Boolea
(4)、传递对象,函数中this指向这个对象
example:
function say1(){
console.log(this); // 输出say1函数的this对象
}
function say2(){};
var sya3 = {name:"blue"};
say1.call(); // window
say1.call(null); // window
say1.call(undefined); // window
say1.call("str"); // String {"str"}
say1.call(1); // Number {1}
say1.call(true); // Boolean {true}
say1.call(say2); // say2函数 ƒ say2(){}
say1.call(say3); // say3对象 sya3:{name:"blue"}
总结apply()和call()方法的区别
apply()和call()两个方法用法基本相同;
第一个参数都是一样的,指向运行在函数的作用域;
唯一不同的就是第二个参数:
apply()方法的第二个参数可以是arguments对象,也可以是个数组实例。
call()方法的第二个参数必须要明确的传入每一个参数,相当于是把数组的项拆开传入,结果都是一样的。很明显通过对比两个方法apply()方法相比call()简洁的多。
至于是使用 apply()还是 call(),完全取决于你采取哪种给函数传递参数的方式最方便。 如果你打算直接传入 arguments 对象,或者包含函数中先接收到的也是一个数组,那么使用 apply() 肯定更方便;否则,选择 call()可能更合适。(在不给函数传递参数的情况下,使用哪个方法都无所谓。)
实际上传参数不是真正使用apply()和call()方法的用武之地;这两个方法真正强大的地方是能扩充函数赖以运行的作用域:
window.color = "red";
var o = {
color:"blue"
}
function sayColor(){
console.log(this.color)
}
sayColor(); // red 执行环境是window
sayColor.call(window); // red 执行环境是window
sayColor.call(this); // red 执行环境是window
sayColor.call(o); // blue 执行环境变成 对象o
在看一个例子:
// 有一个对象banana= {color : "yellow"} ,我们不想对它重新定义 say 方法,
那么我们可以通过 call 或 apply 用 apple 的 say 方法:
var banner = {
color:"yellow"
}
function fruits(){}
fruits.prototype = {
color:"red",
say:function(){
console.log("My color is"+' ' + this.color)
}
}
// console.log(typeof fruits); // 函数
var apple = new fruits;
apple.say(); // red
apple.say.call(banner);
// yellow 所以,可以看出 call 和 apply 是为了动态改变 this 而出现的,
当一个 object 没有某个方法(本例子中banana没有say方法),
但是其他的有(本例子中apple有say方法),我们可以借助call或apply用其它对象的方法来操作。
// console.log(typeof apple); // object
js中还提供了一个bind()方法,这个方法会创建一个函数的实例(必须要创建成一个新函数,否则不能使用),其this值会被绑定到传给bind()函数的值。 bind() IE6,IE7,IE8不 支持
window.color = "red";
var o = {
color:"blue"
}
function sayColor(){
console.log(this.color)
}
sayColor(); // red
var objSayColor = sayColor.bin(o); // blue
//sayColor()调用 bind()并传入对象 o,创建了 objectSayColor()函数。
object- SayColor()函数的 this 值等于 o,因此即使是在全局作用域中调用这个函数,
也会看到"blue"。
bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。
以上就是函数类型的总结。如有错误欢迎留言讨论^^
以下是一些call和apply两个方法使用小技巧:
// 1、数组之间的合并和追加
var arr1 = [1,2,"abc","blue",23];
var arr2 = ["a","b","c","d"];
Array.prototype.push.apply(arr1,arr2);
console.log(arr1); //[1, 2, "abc", "blue", 23, "a", "b", "c", "d"]
// 2、数组中最大值和最小值
var arr3= [2,12,34,5,67,6,9],
var maxNumber1 = Math.max.apply(Math,arr3),
maxNumber2 = Math.max.call(Math,12,3,4,32,66,4,2);
console.log(maxNumber1,maxNumber2); // 67,66
number 本身没有 max 方法,但是 Math 有,我们就可以借助 call 或者 apply 使用其方法。
//3、验证是否是数组(toString()方法没有被重写过)
var arr = [1,2,3,4];
function isArray(obj){
return Object.prototype.toString.call(obj) === "[object Array]"
}
console.log(isArray(arr)); // true
//4、 类(伪)数组使用数组的方法
Javascript中存在一种名为伪数组的对象结构。比较特别的是 arguments 对象,
还有像调用 getElementsByTagName , document.childNodes 之类的,
它们返回NodeList对象都属于伪数组。不能应用 Array下的 push , pop 等方法。
但是我们能通过 Array.prototype.slice.call 转换为真正的数组的带有 length 属性的对象,
这样 domNodes 就可以应用 Array 下的所有方法了。
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
apply、call、bind比较
那么 apply、call、bind 三者相比较,之间又有什么异同呢?何时使用 apply、call,何时使用 bind 呢。简单的一个示例:
var obj = {
x: 10
};
var foo = {
getX: function(){
return this.x;
}
}
console.log(foo.getX.bind(obj)());
console.log(foo.getX.apply(obj));
console.log(foo.getX.call(obj))
三个输出的都是10,但是注意看使用 bind() 方法的,他后面多了对括号。
也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,
使用 bind() 方法。而 apply/call 则会立即执行函数。
apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
apply 、 call 、bind 三者都可以利用后续参数传参;
bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。