JS的函数function

一.JS函数的简介

函数(function):

  • 函数也是一个对象
  • 函数中封装了一些功能(代码), 在需要时可以执行这些功能(代码)
  • 封装到函数的代码不会立即执行, 只有调用时才会执行
  • 调用函数时代码顺序执行, 调用语法: 函数对象()
  • 使用typeof检查一个函数对象时, 会返回function

A.创建一个函数对象

  1. 将要封装的代码以字符串形式传递给构造函数, 例:
1
2
3
4
var fun = new Function("console.log('这是我的第一个函数~~~')");
fun['value'] = "123";//函数是对象, 也可以像对象那样用, 但我们一般不这样用
fun();//这样调用函数
//实际编程中我们很少用这种方式创建一个函数
  1. 使用函数声明来船舰一个函数(对象), 格式:
1
2
3
function 函数名([形参1,形参2,形参3,...,形参n]){
//代码...
}
  1. 使用函数表达式创建一个函数, 格式:
1
2
3
var 函数名 = function([形参1,形参2,形参3,...,形参n]){
//代码...
}

B.参数

  • 在函数的()中, 可以指定0个或多个形参
  • 多个形参之间用逗号隔开, 声明了形参就相当于在函数内部声明了对应的变量
  • 声明的形参并没有被赋值
  • 调用函数时, 可以在()中指定实参, 实参的值会依次赋值给形参
  • 调用函数时, 解析器不会检查形参与实参的数量是否匹配
  • 函数的实参可以是任何类型的值, 包括基本类型、对象、函数(对象).

特别注意: 调用函数时, 解析器也不会检查实参的类型, 所以要检查一下参数是否合法

例题: 定义一个能求两个数和的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function sum(a,b){
if((typeof a) == "number"&&(typeof b) == "number"){
console.log(a+b);
}
}
sum(123,456);
sum(123,"456");//不输出内容
function show(obj) {
console.log("我是" + obj.name + ",性别" + obj.sex + ",我今年" + obj.age + "岁了");
}
var person = {
name:"孙悟空",
sex:"男",
age:500
};
//函数实参为对象
show(person);
function fun(a){
a(person);
}
//函数实参为函数(对象),将max赋值给了a,故a(person);与show(person);等价
fun(max);

C.返回值

  • 在函数中, 可以用return关键字返回一个表达式的值
  • 可以在调用函数处, 声明一个变量接收该函数的返回值
  • 若函数中不写return或没有return一个值, 则会默认return undefined;
  • return语句之后的代码不会被执行
  • 函数的返回值也可以是任意类型的值, 基本类型、对象、函数

例: 定义一个能求两个数最大值的函数, 并将最大值返回.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function max(a,b){
if((typeof a) == "number"&&(typeof b) == "number"){
return a>b?a:b;
}
}
var maxNum = max(123,456);
console.log(maxNum);
function fun(a){
console.log("a = " + a);
}
//函数实参为函数(对象)
fun(max);
//函数实参为函数的返回值
fun(max(123,456));

function fun2(){
//return {name:"孙悟空"};//返回一个对象
function fun3(){
alert("我是fun3函数");
}
//return fun3();//这样是返回fun3函数的返回值
return fun3;//返回fun3这个函数
}
var f = fun2();//此时f为fun3
f();//调用fun3函数
fun2()();//和f();等价,也是调用fun3()函数

D.立即执行函数

  • 函数定义完, 立即就会被调用
  • 这种函数往往是匿名函数, 只会执行一次.例:
1
2
3
4
5
//先用一对小括号将匿名函数括起来,表示是一个整体,否则不允许这样声明函数
(function(a, b) {
console.log("a = " + a);
console.log("b = " + b);
})(123, 456);

E.方法(method)

  • 一类特殊的函数, 这类函数是对象的属性
  • 若一个函数成了对象的一个属性, 那这个函数就叫做对象的方法
  • 调用函数就叫做调用的对象的某方法
1
2
document.write("Hello");//调用document对象的write方法
console.log("Hello");//调用console对象的log方法

二.作用域(Scope)

A.全局作用域

  • 直接编写在script标签中的JS代码, 都在全局作用域中
  • 全局作用域在页面打开时创建, 在页面关闭时销毁
  • 全局作用域中有一个全局对象window, 它代表一个浏览器窗口, 由浏览器创建, 我们可以直接使用
  • 全局作用域中我们创建的变量都会作为window对象的属性保存, 函数作为window的方法
  • 全局作用域里的变量都是全局变量, 在页面的任何部分都能访问到

变量的声明提前: 使用var声明的变量, 会在所有的代码执行之前被声明(但不会被赋值)
但声明变量时不使用var, 则变量不会被提前声明
函数的声明提前: 使用函数声明的形式创建的函数 function 函数名(){}
会在所有代码执行前被创建, 所以我们可以在函数声明前调用它.
但是用函数表达式创建的函数 var fun = function(){};
不会被声明提前, 所以不能在声明前调用.

1
2
3
4
5
6
7
8
9
10
console.log(a);//输出:undefined
var a = 10;//去掉var之后, 第一行报错
fun();
function fun(){
console.log("我是fun");
}
var fun1 = function(){
console.log("我是fun1");
};
fun1();//只能在这之后调用, 否则会报错.

B.函数作用域

  • 函数被调用时, 创建自己的函数作用域, 函数执行完毕, 函数作用域销毁
  • 每调用一次函数, 创建一个新的函数作用域, 作用域之间互相独立
  • 函数作用域中可以访问到全局变量, 但全局作用域无法访问到局部变量
  • 在函数中操作一个变量时, 首先在自身作用域中寻找, 没有的话向上一级寻找
  • 函数中想使用全局变量, 可以使用window.变量名
  • 函数的形参相当于在函数作用域中声明的.

也存在声明提前的问题, 但提前声明于当前作用域

三.debug

这种调试, 可以查看执行过程中内存的状态, 各变变量的情况.

1
2
3
4
5
6
7
8
9
alert(d);//声明提前
var a = 10;

var b = 15;
c = 20;//不要监视c可能会出错卡死
function fun(){
console.log("我是fun");
}
var d = 25;

先打开, 浏览器的开发者工具, 以chrome为例, 按F12, 选择sources, 之后如下图:

debug

其他浏览器类似

四.this

你可以这样理解:

  解析器在调用函数时每次都会向函数内部传递一个隐含的参数this, this指向一个对象, 这个函数我们称为函数执行的上下文对象, 根据函数的调用方式不同, this会指向不同的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function fun(){
console.log(this.name + " " + this);
}
fun();//这时this指向window对象,this.name="全局的name";
var name = "全局的name";
var obj = {
name:"孙悟空",
show:fun
};
obj.show();//这时this指向obj, this.name="孙悟空";
var obj1 = {
name:"猪八戒",
show:fun
};
obj1.show();//这时this指向obj1, this.name="猪八戒";

五.call()和apply()

  • 这两个方法都是函数对象的方法, 需要通过函数对象来调用
  • 当函数对象调用call()和apply()方法时, 函数中的代码也会执行.
  • 可以将一个对象指定为call和apply的第一个参数, 这个对象将会成为函数执行时的this
  • call()方法, 将函数原本的实参在第一个参数(对象)以后依次传递给函数的形参
  • apply()方法, 将函数原本的实参封装到一个数组统一传递.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function fun(){
console.log(this);
}
var obj1 = {name:"obj1",show:function(){
console.log(this.name);
}};
var obj2 = {name:"obj2"};

fun();//输出:window
fun.call(obj1);//输出:object
fun.apply(obj2);//输出:object

obj1.show();//输出:obj1
obj1.show.call(obj2);//输出:obj2

function fun2(a,b){
console.log("a = "+a);
console.log("b = "+b);
}

fun2.call(obj1,1,2);//将参数依次传递
fun2.apply(obj1,[1,2]);//将参数封装成数组统一传递

this的情况:

  1. 在函数中调用时, this永远指window
  2. 在方法中调用时, this是调用方法的对象
  3. 在构造函数中调用时, this是正在创建的对象
  4. 使用call和apply调用时, this是参数传递过来的对象

六.arguments

在调用函数时, 浏览器都会传递进两个隐含的参数:
①函数的上下文对象:this
②封装实参的对象: arguments, 我们传递的实参都保存在里面

  • arguments是一个类数组对象, 它不是数组, 但是它也可以通过数字索引来操作数据.
  • arguments.length可以获取实参的长度, 我们即使没定义形参, 也可以通过arguments来使用形参, 但这样比较麻烦
  • arguments[0]表示第一个实参, 以此类推.
  • arguments.callee属性, 表示当前的函数对象
1
2
3
4
5
6
7
8
function fun(){
console.log(arguments instanceof Array);//判断arguments是不是数组,输出:false
console.log(Array.isArray(arguments));//判断arguments是不是数组,输出:false
console.log(arguments.length);//输出:4
console.log(arguments[3]);//输出:true
console.log(arguments.callee == fun);//输出:true
}
fun("one","two",3,true);

评论