JS函数function.docx
《JS函数function.docx》由会员分享,可在线阅读,更多相关《JS函数function.docx(21页珍藏版)》请在冰点文库上搜索。
![JS函数function.docx](https://file1.bingdoc.com/fileroot1/2023-4/30/2b08df14-b873-41c2-9869-08fcaa8dea01/2b08df14-b873-41c2-9869-08fcaa8dea011.gif)
JS函数function
一、理解函数的本质:
1.函数也相当于数据,只不过是一段代码块,便于反复调用,是进行模块化程序设计的基础,我们可以把它们看作数据(data)。
函数也是数据
作为值得函数
函数本身就是变量,所以函数也可以作为值来使用
functionget(functions,argument){第一个参数接受函数引用,第二个参数接受参数
returnfunctions(argument);
}
functionsum(num){
returnnum+100;
}
console.log(get(sum,100));
输出200
可以像传参数一样把一个函数传递给另一个函数,也可以将一个函数作为另一个函数的结果返回
functionget(functions,argument){
returnfunctions+argument;
}
functionsum(){
return500;
}
console.log(get(sum(),100));
返回600函数作为执行后的值返回
2.JavaScript会为我们定义的每一个函数都创建一个原型对象.所以JavaScript中的每个函数都是一个对象.
函数引用类型:
varfunction=newFunction();
函数对象本质:
在解释器内部,当遇到函数声明这种语法时,就会自动构造一个Function对象,将函数作为一个内部的对象来存储和运行。
调用函数:
可知一个函数对象名称(函数变量)和一个普通变量名称具有同样的规范,都可以通过变量名来引用这个变量,但是函数变量名后面可以跟上括号和参数列表来进行函数调用。
因为函数都是对象,它们有自己的属性和方法。
(name属性length属性prototype属性)
函数的name属性:
通过这个属性可以访问到给定函数指定的名字,这个属性永远等于跟在function关键字后面的标识符.
length属性:
返回定义的函数的形参个数
prototype属性:
原型,定义了toString()call()apply();方法
3.函数和方法的区别?
函数立足于它们自己(例如:
alert()),
而方法是函数内部一个对象的属性(dictionary),我们通过对象来调用方法。
每个JavaScript对象都有一个toString()方法(基于函数原型)
每个JavaScript函数都会有很多附属的(attached)方法,包括toString()、call()以及apply()(基于函数原型)。
二、函数创建
1.函数声明法
functionsum(){}编译器加载的时候会提升函数预先加载到编译器
2.函数表达式字面量法
varsum=function(){};是一条语句后面需要加上分号加载时不提升,顺序加载到时,执行
匿名函数function(){}称为函数表达式
延伸:
采用函数表达式声明函数时,function命令后面不带有函数名。
如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。
varprint=functionx(){
console.log(typeofx);//function
};
x()//xisnotdefined
print()
上面定义的函数同时写了函数名,这个x只在函数体内部可用,指代函数表达式本身,其他地方都不可用。
这种写法的用处有两个:
一是可以在函数体内部调用自身,
二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。
递归计算
varsum=functionfib(num){
if(num>2){
returnfib(num-2)+fib(num-1);//递归调用自己也可用arguments.callee(num)
}else{
return1;
}
}
sum(6)
返回8
注意:
函数的表达式需要在语句的结尾加上分号,表示语句结束。
而函数的声明在结尾的大括号后面不用加分号。
3.匿名函数
function(){}没有函数名,定义时会报错,编译器加载时会误解function为函数声明,所以需要把函数表达式包成代码块(function(){}),
延伸:
立即执行函数
(function(){}())
(function(){})()
voidfunction(){}()定义函数表达式没有返回值或为undefined也是个表达式据说效率最高
newfunction(){}也是立即执行
newfunction(){}()如果传参数,可加上后面的()
立即执行函数的本质:
Javascript的解析器在解析器解析全局的function或者function内部function关键字的时候,默认会把大括号解析成function声明,而不是function表达式。
Javascript引擎看到function关键字之后,认为后面跟的是函数定义语句,所以立即执行函数定义时必须让js引擎消除歧义,不能把函数表达式理解为function函数声明命令
小括号的作用:
小括号能把我们的表达式组合分块,并且每一块,也就是每一对小括号,都有一个返回值,这个返回值实际上也就是小括号中表达式的返回值。
所以,当我们用一对小括号把匿名函数括起来的时候,实际上小括号对返回的,就是一个匿名函数的Function对象。
因此,小括号对加上匿名函数就如同有名字的函数般被我们取得它的引用位置了。
所以如果在这个引用变量后面再加上参数列表,就会实现普通函数的调用形式。
立即执行函数的目的有两个:
一是不必为函数命名,避免了污染全局变量;
二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
延伸:
理解函数声明、函数表达式、匿名函数的区别?
函数声明:
functionfnName(){…};使用function关键字声明一个函数,再指定一个函数名,叫函数声明。
函数表达式varfnName=function(){…};使用function关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。
匿名函数:
function(){};使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数,匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。
@.函数声明和函数表达式不同之处在于?
@1.Javascript引擎在解析javascript代码时会‘函数声明提升’(FunctiondeclarationHoisting)当前执行环境(作用域)上的函数声明,
而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式,
@2.函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName()形式调用。
fnName();
functionfnName(){
...
}
//正常,因为‘提升’了函数声明,函数调用可在函数声明之前
fnName();
varfnName=function(){
...
}
//报错,变量fnName还未保存对函数的引用,函数调用必须在函数表达式之后
varfnName=function(){
alert('HelloWorld');
}();
//函数表达式后面加括号,当javascript引擎解析到此处时能立即调用函数
functionfnName(){
alert('HelloWorld');
}();
//不会报错,但是javascript引擎只解析函数声明,忽略后面的括号,函数声明不会被调用
function(){
console.log('HelloWorld');
}();
//语法错误,虽然匿名函数属于函数表达式,但是未进行赋值操作,
//所以javascript引擎将开头的function关键字当做函数声明,报错:
要求需要一个函数名
在function前面加!
、+、-甚至是逗号等到都可以起到函数定义后立即执行的效果,而()、!
、+、-、=等运算符,都将函数声明转换成函数表达式,消除了javascript引擎识别函数表达式和函数声明的歧义,告诉javascript引擎这是一个函数表达式,不是函数声明,可以在后面加括号,并立即执行函数的代码。
立即执行函数的用途?
javascript中没用私有作用域的概念,如果在多人开发的项目上,你在全局或局部作用域中声明了一些变量,可能会被其他人不小心用同名的变量给覆盖掉,根据javascript函数作用域链的特性,可以使用这种技术可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,所以(function(){…})()内部定义的变量不会和外部的变量发生冲突,俗称“匿名包裹器”或“命名空间”。
JQuery使用的就是这种方法,将JQuery代码包裹在(function(window,undefined){…jquery代码…}(window)中,在全局作用域中调用JQuery代码时,可以达到保护JQuery内部变量的作用。
(function(){
alert('water');
})();
(function(o){
alert(o);
})('water');
(function(o){
alert(o);
returnarguments.callee;//传递多个参数递归调用
})('water')('down');
三.函数声明提升与变量声明提升?
javascript是一门解释性语言,自然没有编译过程,但在脚本执行之前会有语法检查和执行环境的构建,我们把这一过程姑且称为预处理吧。
@1.函数声明,javaScript解析器把函数提升
fn(); //output 2
var fn= function(){
console.log
(1);
}
function fn(){
console.log
(2);
}
fn(); //output 1
首先,后面的fn()函数声明提前,所以第一个fn()为2
其次,后面定义的fn()覆盖前面声明的函数,所以后面执行的函数为字面两定义的函数,结果为1
@2.函数声明、变量声明、arguments参数三者提升优先级?
function fn(t){
t(); @1vart
function t(){ @2.vart=function(){console.log
(1)}
console.log
(2); @3.vart=function(){consol.log
(2)};
} @4.t();
var t = function(){
console.log(3);
}
console.log(t);
}
fn(function(){console.log
(1)}); //output 2
//outputfunction(){console.log(3);}
@1.变量声明vart提升到最顶端
@2.arguments中变量首先会覆盖var方式声明的变量(变量声明提升)
@3.函数声明覆盖相同名字的变量
优先级:
函数形参>函数声明>变量声明(当变量声明遇到已经有同名的时候,不会影响已经存在的属性)
函数形参---->由名称和对应值组成的一个变量对象被创建,如果没有传递对应参数的话,那么由名称和undefined值组成的一种变量对象的属性也被创建
函数声明---->由名称和对应值(函数对象)组成的一个变量对象的属性被创建,如果变量对象已经存在相同的名称属性,则完全替换这个属性
变量声明---->由名称和对应值(undefined)组成一个变量对象的属性被创建,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的
functionaa(a,b,c){@1.a=1b=2c=3@2.a=functiona(){}@3.vara;varaa变量声明提升
functiona(){}
console.log(a);
console.log(aa);
console.log(arguments);
vara='ee';
varaa='444';
arguments=6;
console.log(a);
console.log(aa);
console.log(arguments);
}
aa(1,2,3);
输出结果:
functiona(){}
undefined
[function,2,3]
ee
444
6
函数内部的变量提升
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。
var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
functionfoo(x){
if(x>100){
vartmp=x-100;
}
}
上面的代码等同于
functionfoo(x){
vartmp;
if(x>100){
tmp=x-100;
}
}
所有的变量声明都在范围作用域的顶部,如:
alert("a"inwindow);
vara;
相当于:
vara;
alert("a"inwindow);
这是因为JavaScript引擎首先会扫墓所有的变量声明,然后将这些变量声明移动到顶部。
延伸:
js中没有代码块的概念
js变量声明与赋值
if(!
("a"inwindow)){
vara=1;}
alert(a);
结果:
undefined
分析:
首先,所有的全局变量都是window的属性,语句vara=1;等价于window.a=1;
第二,所有的变量声明都在范围作用域的顶部,如:
alert("a"inwindow);
vara;
相当于:
vara;
alert("a"inwindow);
这是因为JavaScript引擎首先会扫墓所有的变量声明,然后将这些变量声明移动到顶部。
第三,你需要理解该题目的意思是,变量声明被提前了,但变量赋值没有,因为这行代码包括了变量声明和变量赋值。
当变量声明和赋值在一起用的时候,JavaScript引擎会自动将它分为两部以便将变量声明提前,不将赋值的步骤提前是因为他有可能影响代码执行出不可预期的结果。
所以上面的代码就相当于:
vara;
if(!
("a"inwindow)){为false
a=1;}
alert(a);所以为undefined
例子2:
varnum=100;
functionfn(){
varnum=num+1; //varnum=window.num+1;正确,此时才外外面的全局变量num
returnnum;
}
falert(n());答案是NaN
因为在函数体内部首先把varnum提前,这样在赋值的时候去的num+1中的值是undefined,这样进行运算后答案就是NaN,
记住我们找对象的时候是层级往上面找的,找不到才找外面的。
例子3:
varb=(function(){
functionfn(){
return1;
}
returnfn();
functionfn(){
return2;
}
varfn;
fn=3
})();
alert(b);答案是2,
因为首先把varfn提前,然后函数体那样子定义也是一种函数的声明(需要提前),并且同名函数后面覆盖前面的,所以就是var,fn,fn,然后就renturn了,没有走之后的fn=3,所以答案就是2
代码等价于:
varb=(function(){
varfn;
functionfn(){
return2;
}
returnfn();
fn=3
})();
所以输出2
四、函数重复声明,后面的函数会覆盖前面的函数
没有重载:
若定义两个同名函数,后面的会覆盖前面的函数名是一个指针,第二个覆盖了第一个函数的引用
五、函数参数
@1.函数参数传递
js不会主动为你判断你到底给函数传了多少个参数,
如果你多传了,多余的部分就没有被使用,如果你少传了,那么没传的参数值就是undefined
所以我们可以借助arguments的length属性来检测调用函数时是否使用了正确数目的实际参数
同名参数:
取最后出现的那个值
functionf(a,a){
console.log(a);
}
f(1,2)//2
上面的函数f有两个参数,且参数名都是a。
取值的时候,以后面的a为准。
即使后面的a没有值或被省略,也是以其为准。
functionf(a,a){
console.log(a);
}
f
(1)//undefined
调用函数f的时候,没有提供第二个参数,a的取值就变成了undefined。
这时,如果要获得第一个a的值,可以使用arguments对象。
functionf(a,a){
console.log(arguments[0]);
}
f
(1)//1
@2.functionsum(){}定义函数不带参数
定义函数带参数的作用是使用方法时把相应的参数赋值给具体的变量,方便在函数内部使用,如果定义函数时不写也可以通过arguments对象调用传过来的参数
由于JavaScript允许函数有不定数目的参数,所以我们需要一种机制,可以在函数体内部读取所有参数。
这就是arguments对象的由来。
Arguments对象:
该对象代表正在执行的函数和调用他的函数的参数。
[function.]arguments[n]
参数function:
选项。
当前正在执行的Function对象的名字。
说明:
Arguments是进行函数调用时,除了指定的参数外,还另外创建的一个隐藏对象。
arguments对象包含了函数运行时的所有参数,引用一个形式参数可以用参数名,也可以用arguments[]数组形式,arguments[0]就是第一个参数,arguments[1]就是第二个参数,依次类推。
这个对象只有在函数体内部,才可以使用。
arguments.length实参(调用函数时传过来的参数)的长度
arguments.callee.length形参(定义函数时定义的形参个数)的长度
arguments[0]调用实参
arguments[0]=1为参数赋值,改变传过来的值
(2)与数组的关系
需要注意的是,虽然arguments很像数组,但它是一个对象。
某些用于数组的方法(比如slice和forEach方法),不能在arguments对象上使用。
但是,有时arguments可以像数组一样,用在某些只用于数组的方法。
比如,用在apply方法中,或使用concat方法完成数组合并。
用于apply()方法
myfunction.apply(obj,arguments)
Array.prototype.concat.apply([1,2,3],arugments);
将arguments对象转化为数组:
要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。
下面是两种常用的转换方法:
slice方法和逐一填入新数组。
varargs=Array.prototype.slice.call(arguments);或者
varargs=[];
for(vari=0;iargs.push(arguments[i]);
}
arguments对象带有一个callee属性,返回它所对应的原函数。
returnarguments.callee回调函数(把原函数表达式返回,继续调用使用,一般用于级联调用)
使用函数对象的length属性访问形参的长度
arguments对象length属性返回实参的长度
函数对象的length属性,返回函数定义中参数的个数。
functionf(a,b){
alert(f.length)
}
若调用函数时想要省略前面的参数,只有显式传入undefined
functionf(a,b){
returna;
}
f(,1)//error
f(undefined,1)//undefined
六、函数length属性和prototype属性(原型)
每个函数都有两个属性length和prototype
length属性表示函数希望接收的命名参数的个数
prototype属性:
对于ECMAScript中的引用类型而言,prototype是保存他们所有实例方法的真正所在。
诸如:
toString()和valueOf()等方法都保存在prototype下,只不过是通过各自对象的实例访问罢了。
自定义引用类型以及实现继承时,prototype属性的作用及其重要,ECMAScript5中,prototype属性是不可枚举的,因此用for-in无法实现。
每个函数都包含两个非继承而来的方法:
apply()和call()
用途:
在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
call()方法
call方法改变对象指针,指向另一个对象调用
call()方法会用它的第一个参数作为f函数的this指针。
也就是说,我们会告诉运行时,f函数中的this指向的是哪个对象
varx=10;
varo={x:
15};
functionf(){
alert(this.x);
}
f();
f.call(o);原先是全局对象属性为10现在改成o对象属性为15