一 定义方法
1. 静态方法(函数声明表达式)
function 函数名([虚参列表]){
函数体;
[return[函数返回值]]
}
//这是最典型的函数声明,以关键字function开始,其后跟随函数名称标识符、一对圆括号(包含由0个或多个逗号隔开的参数名称)和一对花括号(包含0条或多条JS语句,构成函数体)。这种函数定义方式须要显式的指定函数名称,在代码执行前就被解释器加载到做用域中,这个特性可让咱们在函数定义以前就调用该函数。咱们能够经过代码来验证这一点。
javascript
console.log(sum); //控制台输出sum函数的源代码,此时函数还未定义 function sum(a,b){ return a+b; } console.log(sum(2,3));//5
函数做用域:前端
既然提到函数声明,就要提到函数的做用域。函数做用域是指在函数内声明的全部变量在函数体内始终是可见的,这意味着,变量在声明以前已经可用。这个特性能够被称为声明提早,即在函数体内声明的全部变量,在声明以前已经有定义,但只有在执行到这个变量时才会被真正赋值。从以代码能够清晰地看到这一点java
var scope = "global"; function f(){ console.log(scope); //输出“undefined”,而不是“global” var scope = "local"; //变量在这里赋初始值,但变量自己在函数体内任何地方均是有定义的 console.log(scope); //输出“local” } f();
等价于编程
var scope = "global"; function f() { var scope; //在函数顶部声明了局部变量,即声明提早 console.log(scope); //变量存在,输出“undefined”,而不是“global” var scope = "local"; //变量在这里赋初始值 console.log(scope); //输出“local” } f();
2. 动态匿名方法(构造函数)
var 函数名 = new function(["虚参列表"],“函数体”);
var f = new Function("x","y","return x+y"); //Function()构造函数
var f = function(x,y){return x+y}; //这两条代码是等价的
Function()构造函数能够传入任意数量的字符串实参,最后一个实参所表示的文本是函数体,能够包含任意数量的JavaScript语句。若是构造的函数不包含任何参数,则只需传入一个函数体便可。与前二者方式不一样的是,Function()构造函数容许JavaScript在运行时动态地建立并翻译函数。每次调用Function()构造函数都会解析函数体,并建立新的函数对象。于是,在循环或屡次调用的函数中执行这个构造函数,执行效率会受影响。相比之下,循环中的嵌套函数和函数定义表达式则不会每次执行时都从新编译。
Function()构造函数还有值得注意的一点就是它所建立的函数并非使用词法做用域,函数体代码的编译总在顶层函数执行。以下代码所示:数组
var a = 3; //在顶层函数中声明变量a function f(){ var a = 2; //在函数体内声明局部变量a return new Function("return a*a;"); //没法捕获局部做用域 } console.log(f()()); //控制台输出9而非4,说明构造函数的编译在顶层函数执行 //咱们能够将Function()构造函数认为是在全局做用域中执行的eval()。在实际编程中,Function()构造函数不多用到,前两中定义方法使用比较广泛。
3. 直接量方法
函数名 = function([虚参列表]){函数体;}
与函数定义语句同样,函数直接量表达式也是用到了关键字function。通常这种定义方式适用于将它做为一个大的表达式的一部分,好比在赋值和调用过程当中定义函数。经过函数直接量生成的函数,函数名称能够省略,此时就是一个匿名函数。以下例所示:这样可使代码更为紧凑。函数定义表达式特别适合用来定义那些只会用到一次的函数。app
var f=function(x){ //省略函数名的匿名函数 return x*x; }
等价于函数
var f; f=function(x){ //省略函数名的匿名函数 return x*x; }
//与函数声明表达式不一样的是,函数直接量表达式是在执行到代码时才加载函数的,咱们能够用下面的代码来讲明。this
console.log(f); //控制台输出undefined,此时函数f还未加载 var f=function(x){ //开始加载函数 return x*x; } console.log(f); //控制台输出函数的源代码
二 调用方法
1. 直接调用 函数名(实参列表)
2. 在连接中调用 //<a href="javascript:函数名()"></a>
3. 在事件中调用 事件类型 = "函数名()"
4. 递归调用
(1) 定义 -- 在函数总体内部调用函数自身
(2) 格式
function 函数名(){
代码
函数名();
}
三 方法
1. apply -- 将函数做为对象的方法来调用
将参数以数组形式传递给该方法
spa
foo.apply(obj, [参数1,参数2,参数3.....]) //存在上下文调用的目的就是为了实现方法借用,且不会污染对象。
(1) 若是须要让函数以函数的形式调用, 可使用
foo.apply( null ); // 上下文为 window
--------------------------------------.net
function foo () { console.log( this ); } // 若是须要让函数以函数的形式调用, 可使用 foo.apply( null ); // this => window // 或 foo.apply()
(2) 若是但愿他是方法调用模式, 注意须要提供一个宿主对象
foo.apply( obj ); // 上下文 为 传的 obj 对象
var o = { name: 'jim' };
// 若是但愿他是方法调用模式, 注意须要提供一个宿主对象
foo.apply( o ) // this => o 对象
Note:
使用 apply 进行调用, 若是函数是带有参数的. apply 的第一个参数要么是 null 要么是对象
若是是 null 就是函数调用
若是是 对象就是 方法调用, 该对象就是宿主对象, 后面紧跟一个数组参数, 将函数全部的参数依次放在数组中.
For example:
函数调用:
func( '张三', 19, '男' ), 将其修改为 apply 模式: func.apply( null, [ '张三', 19, '男'] )//以 window 为上下文执行 apply
方法模式:
o.func( 123, 567 ) apply var o = { name: 'jim' }; foo.apply( o, [ 123, 567 ] ); //以 o 为上下文执行 apply
详情例子可参考博文 https://blog.csdn.net/qq_16415157/article/details/53033953
2. call -- 将函数做为对象的方法来调用
将指定参数传递给该方法
函数调用: 函数名.call( null, 参数1,参数2,参数3… )
方法调用: 函数名.call( obj, 参数1,参数2, 参数3… )
不传参时,apply 和 call 彻底同样
function Person ( name, age, gender ) { this.name = name; this.age = age; this.gender = gender; } function Student ( name, age, gender, course ) { // 原型链结构不会变化,同时实现了继承的效果 Person.call( this, name, age, gender );// 借用Person构造方法 this.course = course; } var p = new Student ( 'jim', 19, 'male', '前端' ); console.log( p );
3. bind 方法 (ES 5)让函数绑定对象的一种用法
函数自己就是能够调用, 可是其若是想要做为方法调用, 就必须传入宿主对象, 而且使用 call 或 apply 形式
可是 bind 使得个人函数能够与某一个对象绑定起来, 那么在调用函数的时候, 就好像是该对象在调用方法,就能够直接传参,而不须要传宿主对象。
var write = document.write; write("hello");//不能正确执行,由于write函数丢掉了上下文,此时this的指向global或window对象,致使执行时提示非法调用异常,因此咱们须要改变this的指向 正确的方案就是使用 bind/call/apply来改变this指向 bind方法 var write = document.write; write.bind(document)('hello'); call方法 var write = document.write; write.call(document,'hello'); apply方法 var write = document.write; write.apply(document,['hello']);
4. toString -- 返回函数的字符串表示
function foo(){ return 1; } foo.toString() //"function foo(){return 1;}"
四 arguments对象
1. 功能 -- 存放实参的参数列表
这个函数体内的arguments很是特殊,其实是所在函数的一个内置类数组对象,能够用数组的[i]和.length。
js语法不支持重载!但可用arguments对象模拟重载效果。
重载的定义是指函数的方法名相同,但参数不一样。
例如:
function add(a,b){ console.log(a + b); } add(1,2); function add(c,d,e){ console.log(c + d + e); } add(4,5,6); //输出结果为NaN 9,这就说明后面的函数把前一个同名函数覆盖掉了,从而能够得出js函数不存在重载,永远调用最后一个方法。
Js 函数自己是不存在重载功能的,可是咱们能够利用Arguments对象来进行模拟重载。Javascrip中每一个函数都会有一个Arguments对象实例arguments,它引用着函数的实参,能够用数组下标的方式"[ ]"引用arguments的元素。
以下例子:
function cale(){ //传入一个参数,求平方 if(arguments.length==1){ console.log(arguments[0]*arguments[0]) }else if(arguments.length == 2){ //传入两个参数就求和 console.log(arguments[0]+arguments[1]) } } cale(7); cale(9,5)
//不管传入几个参数均可以求和 function add(){ //arguments:[] //遍历每一个元素并累加 var sum = 0; for (var i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; } console.log(add(5,6,7))
2. 特性
(1) 仅能在函数体内使用
函数对象内,自动建立的专门接收全部参数值的类数组对象。
arguments对象和Function是分不开的。由于arguments这个对象不能显式建立,arguments对象只有函数开始时才可用。
(2) 带有下标属性,但并不是数组
arguments[i]
arguments.length
虽然arguments对象并非一个数组,可是访问单个参数的方式与访问数组元素的方式相同
例如:
arguments[0],arguments[1],...arguments[n],
(3) 函数声明时自动初始化
在js中 不须要明确指出参数名,就能访问它们
例如:
function test(){ var s = "anna" + ","; for (var i = 0; i < arguments.length; i++) { s += arguments[i] + "," } return s; } console.log(test("name", "age"))//anna,name,age
3. 属性
(1) length--获取函数实参的长度
(2) callee--返回当前正在指向的函数
callee 属性是 arguments 对象的一个成员,仅当相关函数正在执行时才可用。
callee 属性的初始值就是正被执行的 Function 对象,这容许匿名的递归函数。
例如:
var sum=function(n){ if(1==n) { return 1; } else { return n + arguments.callee(n-1); } } alert(sum(6)); //通俗一点就是,arguments此对象大多用来针对同个方法多处调用而且传递参数个数不同时进行使用。根据arguments的索引来判断执行的方法。
(3) caler--返回调用当前正在执行函数的函数名
五 函数参数
1. 参数类型
(1) 形参 -- 定义函数时使用的参数
-- 接收调用该参数时传递的参数
(2) 实参 -- 调用函数时传递给函数的实际参数
2. 特性
(1) 参数个数没有限制
实参<形参 -- 多余形参=undefined
形参>实参 -- 多余实参被忽略
(2) 参数的数据类型没有限制
经过arguments对象访问参数数组
(3) 参数始终按值传递
值传参针对基本类型,引用传参针对引用类型,传参能够理解为复制变量值。基本类型复制后俩个变量彻底独立,以后任何一方改变都不会影响另外一方;引用类型复制的是引用(即指针),以后的任何一方改变都会映射到另外一方。
咱们能够把ECMAScript函数的参数想象成局部变量。在向参数传递基本类型的值时,被传递的值被复制给一个局部变量(即命名参数,或者用ECMAScript的概念来讲,就是arguments对象中的一个元素)。在向参数传递引用类型时,会把这个值在内存中的地址(指针)复制给一个局部变量,所以这个局部变量的变化会反映在函数的外部。
a. 基本类型 -- 传值
//由于是按值传递的,传递完后俩个变量各不相干!
function add(num){ num += 10; console.log(num) } var count = 20; add(count)//30 console.log(count)//20
b. 引用类型 -- 传值(这么叫便于理解,其实也是按值传递)
function setName(obj){ obj.name = "anna" } var person = new Object(); setName(person) console.log(person.name)//anna
// 以上代码中建立一个对象,并将其保存在变量person中。而后,这个变量被传递到setName(obj)函数中以后就被复制给了obj。在这个函数内部,obj和person引用的是同一个对象。换句话说,即便ECMAScript说这个变量时按值传递的,但obj也会按引用来访问同一个对象。因而,在函数内部为obj添加name属性后,函数外部的person也将有所反应;由于这时的person和obj指向同一个堆内存地址。因此,不少人错误的认为:在局部做用域中修改的对象会在全局对象中反映出来,就说明参数是按引用传递的。
能够参考下面例子, 证实对象也是按值传递的:
function changeName(obj){ obj.name = "Nico"; obj = new Object();//改变obj的指向,此时obj指向一个新的内存地址,再也不和person指向同一个 obj.name = "Jack;" } var people = new Object(); changeName(people); console.log(people.name)//Nico 证实对象也是按值传递的
//这个例子与前一个惟一的区别,就是setName()函数中添加了两行代码: obj = new Object(); 用来改变obj的指向; obj.name = "Jack"; 用来给新建立的obj添加属性。若是是按引用传递的,那么person就会自动被修改成指向新建立的obj的内存地址,则person的name属性值被修改成"Greg"。可是,当访问person.name时,显示的结果为"Nicholas"。这说明即便在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后被当即销毁!
六 指针标识1. this -- 指向档期那操做对象(将会在新博文中详细解说)2. callee -- 指向参数集合所属函数3. prototype -- 指向函数附带的原型对象(将会在新博文中详细解说)4. constructor -- 指向建立对象的构造函数(将会在新博文中详细解说)