您的位置:首页 > Web前端 > JavaScript

JavaScript引用类型(三)Function函数类型总结

2019-02-27 22:59 162 查看

函数实际上是对象,每个函数都是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()。 4000 这两个方法都是在特定的作用域中调用函数,实际上是等于改变函数的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 则是立即调用 。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: