一 定义方法
1. 静态方法(函数声明表达式)
function 函数名([虚参列表]){
函数体;
[return[函数返回值]]
}
//这是最典型的函数声明,以关键字function开始,其后跟随函数名称标识符、一对圆括号(包含由0个或多个逗号隔开的参数名称)和一对花括号(包含0条或多条JS语句,构成函数体)。这种函数定义方式需要显式的指定函数名称,在代码执行前就被解释器加载到作用域中,这个特性可以让我们在函数定义之前就调用该函数。我们可以通过代码来验证这一点。
console.log(sum); //控制台输出sum函数的源代码,此时函数还未定义 function sum(a,b){ return a+b; } console.log(sum(2,3));//5
函数作用域:
既然提到函数声明,就要提到函数的作用域。函数作用域是指在函数内声明的所有变量在函数体内始终是可见的,这意味着,变量在声明之前已经可用。这个特性可以被称为声明提前,即在函数体内声明的所有变量,在声明之前已经有定义,但只有在执行到这个变量时才会被真正赋值。从以代码可以清晰地看到这一点
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。一般这种定义方式适用于将它作为一个大的表达式的一部分,比如在赋值和调用过程中定义函数。通过函数直接量生成的函数,函数名称可以省略,此时就是一个匿名函数。如下例所示:这样可以使代码更为紧凑。函数定义表达式特别适合用来定义那些只会用到一次的函数。
var f=function(x){ //省略函数名的匿名函数 return x*x; }
等价于
var f; f=function(x){ //省略函数名的匿名函数 return x*x; }
//与函数声明表达式不同的是,函数直接量表达式是在执行到代码时才加载函数的,我们可以用下面的代码来说明。
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 -- 将函数作为对象的方法来调用
将参数以数组形式传递给该方法
foo.apply(obj, [参数1,参数2,参数3.....]) //存在上下文调用的目的就是为了实现方法借用,且不会污染对象。
(1) 如果需要让函数以函数的形式调用, 可以使用
foo.apply( null ); // 上下文为 window
--------------------------------------
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 -- 指向创建对象的构造函数(将会在新博文中详细解说)