每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法;由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定
1.函数声明和函数表达式
- 定义函数的方式有2种:函数声明和函数表达式
- 写法区别:
function getName(name){ //函数声明,使用关键字function
console.log(name);
}
var getName = function(name){ //函数表达式,将函数整体赋值给变量,这种情况下创建的函数叫做匿名函数
console.log(name);
}
- 声明前置区别:
getName("hunger"); //hunger,因为函数声明会前置(类似变量提升)
function getName(name){
console.log(name);
}
getName("hunger"); //报错getName is not a function,因为函数表达式没有前置
var getName = function getName(name){
console.log(name);
}
2.变量的声明前置与函数的声明前置
- 变量声明前置
console.log(a); //undefined,变量提升,将var a;提升至作用域最前面;当执行到var a = 1;时再进行赋值
var a = 1;
- 函数声明前置(
function declaration hoisting
):执行代码之前会先读取函数声明
getName("hunger"); //hunger,函数声明前置,将整个函数放置作用域最前面
function getName(name){
console.log(name);
}
3.参数
- ECMAScript中的参数在内部是用一个数组来表示的
- 实际上,函数内部可以通过
arguments
——这个类数组对象访问参数列表,该对象具有length属性,仅在函数内部有效
function getInfo(name,age,sex){
console.log(name);
console.log(age);
console.log(sex);
console.log(arguments); //["Amy", 17, "female"],实参具体内容
console.log(arguments.length); //3,实参的个数
console.log(arguments[1]); //17,可通过下标操作实参
}
getInfo("Amy",17,"female"); //Amy 17 femal
- 没有传递值的命名参数,默认为
undefined
;就像只声明变量但没有初始化一样
function sum(num1,num2,num3){
console.log(num1);
console.log(num2);
console.log(num3);
}
sum(1,2); //1 2 undefined
4.函数的重载
- 函数重载:相同名字的函数参数个数不同或者顺序不同、接受的参数类型不同都被认为是不同的函数
- 在JS中没有函数重载的概念,函数只通过名字确定唯一性,即使参数不同也被认为是相同的函数,后面的覆盖前面的
function getInfo(name){
console.log("name"+name);
}
function getInfo(name,age){
console.log("name:"+name+","+"age:"+age);
}
getInfo("Amy"); //name:Amy,age:undefined,覆盖了第一个function
- 实现重载:可以使用
arguments
判断参数类型或者个数等分情况操作
function getArguments(){
if(arguments.length == 1){
var name = arguments[0];
getInfo1(name);
}else{
var name = arguments[0];
var age = arguments[1];
getInfo2(name,age);
}
}
function getInfo1(name){
console.log("name:"+name);
}
function getInfo2(name,age){
console.log("name:"+name+","+"age:"+age);
}
getArguments("Amy"); //name:Amy
getArguments("Asher",4); //name:Asher,age:4
5.立即执行函数表达式是什么?有什么作用
- 立即执行函数是一种表达式
(function(){})();
可以理解为(函数定义表达式)函数调用表达式
(function f(f){
return typeof f();
})(function(){return 1;}); // "number"
- 立即执行函数模仿块级作用域:JS本身是没有块级作用域的概念的
(function(){
//这里就是块级作用域
})()
6.写一个函数,返回参数的平方和
function sumOfSquares(){
var sum = 0;
for(var i=0;i<arguments.length;i++){
sum += arguments[i] * arguments[i];
}
console.log(sum);
}
sumOfSquares(2,3,4); // 29
sumOfSquares(1,3); // 10
7.以下代码分别输出什么,为什么
console.log(a); //undefined 变量提升
var a = 1;
console.log(b); //Uncaught ReferenceError: b is not defined 因为b没有声明
sayName('world'); //hello world 函数声明前置
sayAge(10); //Uncaught TypeError: sayAge is not a function 函数表达式没有前置
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
function fn(){}
var fn = 3;
console.log(fn); //3 分别声明前置和变量提升,然后fn被赋值为3
//浏览器引擎将以上代码理解为:
function fn(){} //首先,同一作用域内重复声明,函数声明会提升至变量声明之前
var fn;
fn = 3;
function fn(fn2){
console.log(fn2); //fn2函数体
var fn2 = 3;
console.log(fn2); //3
console.log(fn); //fn函数体
function fn2(){
console.log('fnnn2');
}
}
fn(10);
//可理解为以下伪代码:
function fn(fn2){
var fn2 = 10; //首先,传参,赋值
function fn2(){ //函数声明前置并且提升至变量声明之前
console.log('fnnn2');
}
var fn2; //变量声明前置
console.log(fn2);
fn2 = 3; //赋值,fn2的值改变
console.log(fn2);
console.log(fn);
}
fn(10);
var fn = 1;
function fn(fn){
console.log(fn);
}
console.log(fn(fn));
//Uncaught TypeError: fn is not a function
//函数声明前置之后被变量赋值覆盖,因此最后 fn=1 而报错
fn();
var i = 10;
var fn = 20;
console.log(i); //10
function fn(){
console.log(i); //undefined
var i = 99;
fn2();
console.log(i); //100
function fn2(){
i = 100;
}
}
//可理解为以下伪代码:
function fn(){
function fn2(){
i = 100;
}
var i;
console.log(i); //此时i还并没有被赋值,因此返回undefined
i = 99;
fn2(); //执行fn2之后,i被赋值为100,覆盖 i = 99;
console.log(i); //因此这时返回100
}
var i;
var fn;
fn();
i = 10;
fn = 20;
console.log(i); //遵循作用域链,全局作用域下的i返回10
var say = 0;
(function say(n){
console.log(n); //依次返回10,9,8,7,6,5,4,3,2 立即执行函数,即声明之后立即执行
if(n<3) return;
say(n-1);
}( 10 ));
console.log(say); //0 返回全局变量say,此时变量say已覆盖函数say