javascript学习总结之函数
前言
在学习javascript函数的时候,有几个经常很容易混淆的方法,call,apply,bind,caller,callee,这些方法的使用,在此通过查阅相关书籍和资料,整理了一篇博客,本文将详细介绍call,apply,bind,caller,callee这些方法的使用,如果有讲解有歧义的地方,还请大家海涵。可以多多和我交流,在下方留下您宝贵的评论。
Function函数
在学习call,apply,bind,caller,callee这些方法之前,我们需要先了解函数到底是什么东西,毕竟这些方法都是围绕函数进行展开的。
函数的定义:
Function 构造函数 创建一个新的Function对象,在 JavaScript 中,每个函数实际上都是一个Function对象,使用Function构造器生成的Function对象是在函数创建时解析的。这比你使用函数声明或者函数表达式(function)并在你的代码中调用更为低效,因为使用后者创建的函数是跟其他代码一起解析的。
- 所有被传递到构造函数中的参数,都将被视为将被创建的函数的参数,并且是相同的标示符名称和传递顺序
- javascript中的函数就是对象,对象就是“键/值”对的集合并拥有一个连接到原型对隐藏连接
- 每个函数都是Function类型的实例,而且都与其它引用类型一样具有属性和方法
- 由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定
函数的定义有两种方法,第一种是函数声明,第二种是函数表达式
函数声明
console.log(foo(10,10));//
function foo(a,b){
return a+b
}
函数表达式
console.log(sum(10,10));//TypeError: sum is not a function
var sum=function(a,b){
return a+b;
}
同样是用来表达函数的方式,为什么函数声明和函数表达式的差别那么大呢?且听我慢慢说来,解析器在向执行函数环境中加载数据时,对函数声明和函数表达式并非一视同仁,解析器会率先读取函数声明,并使其在执行任何代码之前可用(也就是我们常说的变量提升),至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正的解析执行。
对代码求值时,javascript引擎在第一遍会声明函数并将它们放到源代码树的顶部,所以,即使声明函数的代码在调用它的代码后面,javascript引擎也能把函数声明提升到顶部。
没有重载(深入理解)
很多编程语言中都有重载这个概念,比如java中的方法重载,构造函数重载等等,但是JavaScript这一门动态语言中偏偏没有方法重载,我们来看下示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>没有重载(深入理解)</title>
</head>
<body>
<script type="text/javascript">
function addSomeNumber(number){
return number+100;
}
function addSomeNumber(num){
return num+200;
}
var result=addSomeNumber(100);
console.log(result);//300
</script>
</body>
</html>
显然,这个例子中声明了两个同名函数,而结果则是后者的函数覆盖了前面的函数,以上代码实际上与下面的代码没有什么区别
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>没有重载(深入理解)</title>
</head>
<body>
<script type="text/javascript">
var addSomeNumber=function(number){
return number+100;
}
addSomeNumber=function(num){
return num+200;
}
var result=addSomeNumber(100);
console.log(result);//300
</script>
</body>
</html>
通过重写之后的代码,很容易看清楚到底是怎么回事,在创建第二个函数时,实际上覆盖了引用第一个函数的变量addSomeNumber。
作为值的函数
因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用,也就是说不仅可以像传递参数一样把一个函数传递给另一个函数,而且还可以将一个函数作为另一个函数的结果返回,比如常见的回调函数就是。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>作为值的函数</title>
</head>
<body>
<script type="text/javascript">
function callSomeFunction(someFunction,someArgumet){
return someFunction(someArgumet);
}
function add(num){
return num+10
}
var result1=callSomeFunction(add,10);
console.log(result1);//20
function getGreeting(name){
return 'hello'+name;
}
var result2=callSomeFunction(getGreeting,'一只流浪的kk');
console.log(result2);//hello 一只流浪的kk
</script>
</body>
</html>
callSomeFunction函数接收两个参数,第一个参数是一个函数,第二个参数是要传递给该函数的一个值,当然,可以从一个函数中返回另一个函数,我们看下面的示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>作为值的函数</title>
</head>
<body>
<script type="text/javascript">
function comPare(propertyName){
return function(a,b){
var value1=a[propertyName];
var value2=b[propertyName];
if(value1>value2){
return -1
}else if(value1<value2){
return 1;
}else{
return 0;
}
}
}
var data=[{name:'zhangsan',age:28},{name:'lisi',age:29}];
data.sort(comPare('name'));
console.log(data);//[{name:'lisi',age:29},{name:'zhangsan',age:28}];
data.sort(comPare('age'));
console.log(data);//[{name:'lisi',age:29},{name:'zhangsan',age:28}];
</script>
</body>
</html>
这个函数定义看起来有点复杂,但实际上无非就是在一个函数中嵌套了另一个函数,而且内部函数前面加了一个return操作符,在内部函数接收到propertyName参数后,它会使用方括号表示法取得给定属性的值,取得了想要的属性值之后,定义比较函数就非常简单了。
函数内部属性arguments和this
在函数内部,有两个特殊的对象:arguments和this,其中arguments它是一个类数组,包含着传入函数中的所有参数,this引用的是函数执行的环境对象,或者也可以说是this的值(当在网页的全局作用域中调用函数时,this对象的引用就是window)
arguments
<script type="text/javascript">
function counter(){
var sum=0;
for(var i=0;i<arguments.length;i++){
sum+=arguments[i];
}
return sum;
} console.log(counter(199,991,1,2,3,4,5));//1205
console.log(counter());//0
</script>
这里的arguments是一个隐式对象,不声明也在函数中,内部函数可以访问外部函数的任意内容,但是不能直接访问外部函数的arguments与this对象
<script type="text/javascript">
function f1(){
console.log(arguments.length);//
f2=function(){
console.log(arguments.length);//
}
return f2;
}
var f=f1(1,2,3);
f();
</script>
this
<script type="text/javascript">
window.color='red';
var o={
color:'blue'
};
function sayColor(){
console.log(this.color);//red;
}
sayColor();
o.sayColor=sayColor
o.sayColor();//blue
</script>
上面这个函数sayColor()是在全局作用域中定义的,它引用了this对象,由于在调用函数之前,this的值并不确定,因此this可能会在代码执行过程中,引用不同的对象,当在全局作用域中调用sayColor()时,this引用的是全局对象window,换句话说,对this.color求值会转换成window.color求值,于是结果就返回red,而当把这个函数赋给对象o并调用o.sayColor()时,this的引用的是对象o,因此对this.color求值转换成o.color求值,结果返回blue。
注意:函数的名字仅仅是一个包含指针的变量而已。
构造函数
在javascript中对象构造函数可以创建一个对象
<script type="text/javascript">
/*构造函数*/
//可以简单的认为是一个类型的定义
function Student(name,age){
this.name=name;
this.age=age;
this.show=function(){
console.log(this.name+","+this.age);
}
} //通过new关键字调用构造函数,创建一个对象tom
var rose=new Student("rose",18);
var jack=new Student("jack",20); rose.show();//rose,18
jack.show();//jack,20
</script>
学会了函数的相关知识之后,我们就开始学习call(),apply(),caller,callee,bind()的相关用法,一起来看看吧!
函数的属性和方法
callee
当函数被调用时,它的arguments.callee对象就会指向自身,也就是一个对自己的引用,可能描述的不是太清楚,我们通过案例进行讲解,最常见的示例就是递归了,我们看下示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script type="text/javascript">
function factorial(num){
if(num<=1){
return 1;
}else{
return num*factorial(num-1)
}
}
console.log(factorial(5));//120
</script>
</body>
</html>
这个示例中有一个非常明显的弊端,就是这个函数的执行与函数名factorial仅仅耦合在了一起,我们编程讲究的是高内聚,低耦合。为了解决这个问题,我们就会使用arguments.callee来代替函数名。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script type="text/javascript">
function factorial(num){
if(num<=1){
return 1;
}else{
return num*arguments.callee(num-1)
}
}
console.log(factorial(5));//120
</script>
</body>
</html>
这样一来我们就大大的降低了耦合度,无论函数名是什么,怎么执行都不会有影响
caller
这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>caller的使用</title>
</head>
<body>
<script type="text/javascript">
function outer(){
inner();
}
function inner(){
console.log(inner.caller);
}
outer();
</script>
</body>
</html>
结果:
以上代码输出outer()函数的源代码,因为outer()调用了inner(),所以inner.caller就指向outer(),为了实现更松散的耦合,也可以通过arguments.callee.caller来访问相同的信息。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>caller的使用</title>
</head>
<body>
<script type="text/javascript">
function outer(){
inner();
}
function inner(){
console.log(arguments.callee.caller);
}
outer();
</script>
</body>
</html>
输出的结果和之前的一样,在这里我们使用arguments.callee代替了inner
caller和this的区别
this是指调用方法的对象,而caller是指调用函数的函数
<script type="text/javascript">
function add(n)
{
console.log("add被调用");
if(n<=2){
return 1;
}
return add.caller(n-1)+add.caller(n-2);
} function calc(n){
console.log("calc被调用");
return add(n);
} //1 1 2
console.log(calc(3));
</script>
总结
- 每个函数都包含两个非继承而来的方法,apply()和call(),这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内的this对象的值。
- apply()方法接收两个参数,一个是在其中运行函数的作用域,另一个是参数数组,第二个参数可以是array示例,也可以是arguments对象。
- call()方法和apply()方法的作用相同,它们的区别在于接收参数的不同,对于call()而言,第一个参数是this的值没有变化,变化的是其余参数都直接传递给函数,换句话说,在使用call()方法时,传递给函数的参数必须每个列举出来。
call()
Function.call(obj,[param1[,param2[,…[,paramN]]]])
obj:这个对象将代替Function类里this对象
params:这个是一个参数列表
调用一个对象的一个方法,以另一个对象替换当前对象
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>call()方法的使用</title>
</head>
<body>
<script type="text/javascript">
function sum(num1,num2){
return num1+num2;
}
function callSum(num1,num2){
return sum.call(this,num1,num2);
}
console.log(callSum(10,10));//20
</script>
</body>
</html>
在使用call()方法的情况下,callSum()必须明确地传入每一个参数
apply()
Function.apply(obj,args)方法能接收两个参数
obj:这个对象将代替Function类里this对象
args:这个是数组,它将作为参数传给Function(args-->arguments)
注意
- 如果 argArray 不是一个有效的数组或者不是arguments对象,那么将导致一个 TypeError
- 如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>apply()方法的使用</title>
</head>
<body>
<script type="text/javascript">
//定义一个人类
function Person(name,age){
this.name=name;
this.age=age;
}
//定义一个学生类
function Student(name,age,grade){
Person.apply(this,arguments);//此时的this指代Studnent
this.grade=grade;
}
//创建一个学生
var mark=new Student('zhagnsan',21,'七年级');
console.log(mark.name,mark.age,mark.grade);//zhangsan,21,七年级
</script>
</body>
</html>
特别奇怪的现象,我们明明没有给name和age属性赋值,为什么又存在这两个属性的值呢?
分析:Person.apply(this,arguments);
this:在创建对象在这个时候代表的是student
arguments:是一个数组,也就是[“zhangsan”,”21”,”一年级”];
也就是通俗一点讲就是:用student去执行Person这个类里面的内容,在Person这个类里面存在this.name等之类的语句,这样就将属性创建到了student对象里面。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>apply()方法的使用</title>
</head>
<body>
<script type="text/javascript">
function sum(num1,num2){
return num1+num2;
}
function callSum1(num1,num2){
return sum.apply(this,arguments);
}
function callSum2(num1,num2){
return sum.apply(this,[num1,num2]);
}
console.log(callSum1(10,10));//20
console.log(callSum2(10,10));//20
</script>
</body>
</html>
在上面这个示例中,callSum1()在执行sum()函数时传入了this作为this的值(因为是在全局作用域中调用的,所以传入的值就是window对象)和arguments对象,二callSum2()同样也调用了sum()函数,但它传入的则是this和一个参数数组,这两个函数都会正常执行并返回结果。
注意:在严格模式下,为指定环境对象而调用函数,则this值不会转型为window,除非明确把函数添加到某个对象或者调用apply()或call(),否则this的值是undefined
什么情况下用apply(),什么情况下用call()?
在给对象参数的情况下,如果参数的形式是数组的时候,比如apply示例里面传递了参数arguments,这个参数是数组类型,并且在调用Person的时候参数的列表是对应一致的(也就是Person和Student的参数列表前两位是一致的) 就可以采用 apply , 如果我的Person的参数列表是这样的(age,name),而Student的参数列表是(name,age,grade),这样就可以用call来实现了,也就是直接指定参数列表对应值的位置(Person.call(this,age,name,grade));
事实上,传递参数并非apply()和call()真正的用武之地,它们真正强大的地方是能够扩充函数赖以运行的作用域,看如下示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script type="text/javascript">
window.color='red';
var o={
color:'blue'
}
function sayColor(color){
console.log(this.color)
}
sayColor();//red
sayColor.call(this);//red
sayColor.call(window);//red
sayColor.call(o);//blue
</script>
</body>
</html>
使用call()或者apply()来扩充作用域的最大好处,就是对象与方法不需要任何耦合关系
bind()
这个方法会创建一个函数的实例,其this的值会被绑定到传给bind()函数的值
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>bind方法的使用</title>
</head>
<body>
<script type="text/javascript">
window.color='red';
var o={
color:'blue'
}
function sayColor(color){
console.log(this.color)
}
var objSayColor=sayColor.bind(o);
objSayColor();//blue
</script>
</body>
</html>
在这里,sayColor()调用bind并传入对象o,创建了objSayColor()函数,objSayColor()函数的this的值等于o,因此即使全局作用域中调用这个函数,也会看到blue
length
在声明函数时指定的命名参数的个数
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Function</title>
</head>
<body>
<h2>Function - length</h2>
<script>
function f1(n1,n2)
{
console.log("实际带入的参数个数:"+arguments.length);//1
}
console.log("定义的命名参数个数:"+f1.length);
f1(1);
f1(1,2,3);
</script>
</body>
</html>
apply()方法的妙处
apply在实际应用中有非常之多的妙处,在这里我就补充两点,一种是array种push的短板,二是使用Math.Max(),Math.min()求最大值,最小值等等。
(1)Array.prototype.push 可以实现两个数组合并
我们知道数组的push方法没有提供push一个数组,但是提供了push(param1,param,…paramN) 所以同样也可以通过apply来装换一下这个数组,我们看下示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>apply的应用一</title>
</head>
<body>
<script type="text/javascript">
var arr1=[1,2,3];
var arr2=[4,5,6];
Array.prototype.push.apply(arr1,arr2);
console.log(arr1);//[1,2,3,4,5,6]
</script>
</body>
</html>
也可以这样理解,arr1调用了push方法,参数是通过apply将数组装换为参数列表的集合
(2)Math.max()和Math.min()求数组的最大值和最小值
Math.max(a,b)和Math.min(a,b)只能求两个数中的最大值和最小值,但是我们想要求数组中的最大值和最小值的时候却无法实现,而使用apply方法可以巧妙的实现,如下示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>apply的应用二</title>
</head>
<body>
<script type="text/javascript">
var arr=[1,2,3,4,5];
console.log(Math.max.apply(null,arr));//5
console.log(Math.min.apply(null,arr));//1
</script>
</body>
</html>
我们看到巧妙的使用apply可以非常简单的实现Math.max()和Math.min()求最大值和最小值
总结
call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就不一样了,总结如下
- call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 Function.call(this.Obj,arg0,arg1,...)
- apply 的所有参数都必须放在一个数组里面传进去 Function.apply(thisObj,[arguments])
- bind 除了返回是函数以外,它 的参数和 call 一样
当然,三者的参数不限定是 string 类型,允许是各种类型,包括函数 、 object 等等
javascript学习总结之函数的更多相关文章
- JavaScript学习03 JS函数
JavaScript学习03 JS函数 函数就是包裹在花括号中的代码块,前面使用了关键词function: function functionName() { 这里是要执行的代码 } 函数参数 函数的 ...
- 廖雪峰 JavaScript 学习笔记(函数)
JavaScript中,定义函数的方式如下: function abs(x) { if (x >= 0) { return x; } else { return -x; } } 上述abs()函 ...
- javascript学习—理解addLoadEvent函数
onload事件是HTML DOM Event 对象的一个属性,又叫事件句柄(Event Handlers),它会在页面或图像加载完成后(注意是加载完成后)立即发生. window.onload = ...
- javascript学习笔记--迭代函数
概要 这里的迭代函数指的是对数组对象的操作方法,js数组共有五个迭代函数:every.fifter.forEach.map.some. 1.every every方法,返回值为Boolean类型,tr ...
- JavaScript学习03(函数)
函数 函数定义 JavaScript 函数是通过 function 关键词定义的. 声明定义 function functionName(parameters) { 要执行的代码 } 被声明的函数不会 ...
- 【JavaScript学习笔记】函数、数组、日期
一.函数 一个函数应该只返回一种类型的值. 函数中有一个默认的数组变量arguments,存储着传入函数的所有参数. 为了使用函数参数方便,建议给参数起个名字. function fun1(obj, ...
- 前端学习 第二弹: JavaScript中的一些函数与对象(1)
前端学习 第二弹: JavaScript中的一些函数与对象(1) 1.apply与call函数 每个函数都包含两个非继承而来的方法:apply()和call(). 他们的用途相同,都是在特定的作用域中 ...
- JavaScript学习09 函数本质及Function对象深入探索
JavaScript学习09 函数本质及Function对象深入探索 在JavaScript中,函数function就是对象. JS中没有方法重载 在JavaScript中,没有方法(函数)重载的概念 ...
- JavaScript学习总结-技巧、有用函数、简洁方法、编程细节
整理JavaScript方面的一些技巧.比較有用的函数,常见功能实现方法,仅作參考 变量转换 //edit http://www.lai18.com var myVar = "3.14159 ...
随机推荐
- 【Android - 进阶】之Drawable简介
Drawable是什么?Android给我们的解释是:“A general abstraction for 'something that can be drawn'.”,翻译过来就是:对于可以绘制的 ...
- JPA配置实体时 insertable = false, updatable = false
当使用JPA配置实体时,如果有两个属性(一个是一般属性,一个是多对一的属性)映射到数据库的同一列,就会报错. 这时,在多对一的@JoinColumn注解中添加insertable = false, u ...
- C#程序编写高质量代码改善的157个建议[正确操作字符串、使用默认转型方法、却别对待强制转换与as和is]
前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理解的东西,有些地方可能理解的不太到位,还望指正. 建议1. ...
- HC大会,华为联合合作伙伴发布一站式物联网IoT开发工具小熊派BearPi
传统的物联网产品开发步骤复杂,涉及硬件开发.软件开发.云端开发等众多流程.而且产品的开发周期长.开发成本高.产品稳定性不佳.维护成本高.而物联网设备本身市场竞争激烈,价格低,设备更新迭代快,所以在保证 ...
- 深入理解Android异步消息处理机制
一.概述 Android 中的异步消息处理主要分为四个部分组成,Message.Hndler.MessageQueue 和 Looper.其关系如下图所示: 1. Message 是线程之间传递的消息 ...
- python 拷贝文件夹下的文件 到 另一个文件夹
import os,shutil def copy_search_file(srcDir, desDir): ls = os.listdir(srcDir) for line in ls: fileP ...
- NIM游戏,NIM游戏变形,威佐夫博弈以及巴什博奕总结
NIM游戏,NIM游戏变形,威佐夫博弈以及巴什博奕总结 经典NIM游戏: 一共有N堆石子,编号1..n,第i堆中有个a[i]个石子. 每一次操作Alice和Bob可以从任意一堆石子中取出任意数量的石子 ...
- (全国多校重现赛一)A-Big Binary Tree
You are given a complete binary tree with n nodes. The root node is numbered 1, and node x's father ...
- 【新手必学】Python爬虫之多线程实战
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理.作者:清风化煞_ 正文 新手注意:如果你学习遇到问题找不到人解答,可以点 ...
- Python3 网络编程和并发编程总结
目录 网络编程 开发架构 OSI七层模型 socket subprocess 粘包问题 socketserver TCP UDP 并发编程 多道技术 并发和并行 进程 僵尸进程和孤儿进程 守护进程 互 ...