创建函数
通过字面量(直接量)创建函数
创建函数的字面量形式有两种:函数声明和函数表达式。它们除了在语法上有一些差异外,其他作用都是一样的。
函数声明:
函数声明会被提升到当前作用域的最顶部,因此我们可以在函数声明之前使用它。
max(23, 56); // 56
function max(x, y) {
if(x > y) {
return x;
} else {
return y;
}
}
函数表达式:
变量声明会被提升到当前作用域的最顶部,而赋值并不会被提升,因此需要先声明赋值,然后再使用,否则会报错。
max(23, 56); // 抛出错误:max is not a function
var max = function (x, y) {
if(x > y) {
return x;
} else {
return y;
}
};
可以用下面的代码来解释为什么会抛出错误:
var max; // undefind
console.log(max(45, 90)) // max is not a function
max = function (x, y) {
if(x > y) {
return x;
} else {
return y;
}
};
变量声明被提升到了当前作用域的最顶部,而赋值并没有被提升,因此会抛出错误。
建议大家无论在使用哪种字面量的方式创建Function类型的对象时,一定要先声明赋值,然后再使用。
通过构造器创建函数
但是这种方式不易于阅读和调试,因此不建议使用这种方式。
var max = new Function('x', 'y', 'if(x > y) { return x;} else {return y;}');
函数的参数
形参:创建函数时,指定的参数。
实参:调用函数时,实际传递给函数的参数。
function getArea (width, height) {
// 获取实参的数量
console.log(arguments);
// 通过索引使用实参
console.log(arguments[0])
console.log(arguments[1])
console.log(arguments[2])
return width * height;
};
getArea(20,40)
获取函数形参的数量
console.dir(getArea.length);
获取实参的数量
function getArea (width, height) {
// 获取实参的数量
console.log(arguments.length);
return width * height;
};
函数并不会限制实参的数量,你可以不传递实参,或传递任意数量的实参。
console.log(getArea());
console.log(getArea(123));
console.log(getArea(123, 678));
console.log(getArea(123, 678, 999))
函数另一独特之处是无论我们传递给它多少个参数它都不会报错。这是因为我们传递给函数的实参实际上被保存到了一个类似于数组的对象中,arguments,数组可以包含任意数量的元素,所以可以我们向函数传递任意数量的实参。我们在访问这些参数的时候可以通过数组下表的方式访问。
function getArea (width, height) {
// 获取实参的数量
console.log(arguments);
// 通过索引使用实参
console.log(arguments[0])
console.log(arguments[1])
console.log(arguments[2])
// 返回计算之后面积
return width * height;
};
既然我们可以通过索引使用实参,那么我们的函数还需要写形参吗?答案是需要的。
如果函数没有形参,那么在使用实参时每次都要写上arguments,而且必须要仔细分析函数体内的代码才能知道函数需要几个参数,才能明白每个参数的含义。
而有了形参就不一样了,不仅书写简单,参数的含义容易理解,很轻松地就能明白函数的功能。
所以很多开发人员不愿意使用arguments对象。例如:
function getArea (width, height) {
// return arguments[0] + arguments[1];
return width * height;
};
// 形参的存在就是为了方便我们去使用实参
// 1. 方便书写
// 2. 方便我们理解参数的含义,例如,width,height 就比 arguments[0],arguments[1] 容易理解
然而,有些时候使用arguments比使用形参更高效。比如,我们要创建一个用来求和的函数,它可以接受任意数量的参数,并返回它们的和。这个时候我们就不能使用形参,因为我们不知道会传递过来多少个实参,因此,使用arguments是最好的选择。
function sum() {
var result = 0,
i = 0,
len = arguments.length;
while(i < len){
result += arguments[i];
i++;
}
return result;
}
console.log(sum(2, 5, 7, 5, 5 ,90));
函数的返回值
由于函数作用域的存在,函数外部是无法访问到函数内部的。但是我么可以通过 return 语句将函数内部的数据返回到函数外部,这样的话,我们就可以拿到函数内部的数据了。
function max(x, y) {
var result;
if(x > y) {
result = x;
} else {
result = y;
}
return result; // 将比较之后的最大值返回到函数外部,否则函数外部是访问不到 result 变量的。
};
console.log(max(23, 45))
return 语句是可以多次使用的,但只会有一个 return 语句被执行。因为 return 语句表示函数的结束,return语句之后所有代码都不会被执行了。
function max(x, y) {
if(x > y) {
return x;
} else {
return y;
}
};
console.log(max(23, 45))
模仿函数重载
概念:在同一个作用域下,可以有组具有相同函数名,不同参数列表(形参的数量和类型不同)的函数,这样一组函数被称为重载函数。
作用:函数重载通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了命名冲突,对函数的可读性有很大的好处。
// 用于计算两个整数的和
int sum(int a, int b){
}
// 用于合并2个字符串
string sum(string a, string b){
}
// 用于合并两个数组
int[] sum(int[] a, int[] b){
}
sum(123, 456); // 自动调用第1个函数
sum("aaa","bbb"); // 自动调用第2个函数
sum(["aaa","bbb"], [123, 456]); // 自动调用第3个函数
遗憾的是JavaScript并不支持函数重载,如果在同一个作用域下出现了多个具有相同名称的函数,前面的函数会被最后一个声明的函数覆盖掉。例如:
function sum(a, b){
console.log('计算两个整数的和');
}
function sum(a, b){
console.log('合并2个字符串');
}
sum(123, 456); // '合并2个字符串'
虽然JavaScript不支持函数重载,但是不代表我们不能模仿函数重载。我们可以通过arguments检测传入参数的数量,然后根据不同的数量来决定做什么操作。例如:
function sum(a, b) {
// 判断实参的数量是否为 2
if(arguments.length == 2) {
if(typeof a === 'number' && typeof b === 'number') {
return a + b;
} else if(typeof a === 'string' && typeof b === 'string') {
return a + b;
} else if(a instanceof Array && b instanceof Array) {
return a.concat(b);
} else {
throw new Error('参数的类型必须为:数字,字符串或数组');
}
} else {
throw new Error('函数需要2个参数,而你却传了'+arguments.length+'个参数');
}
}
console.log(sum(1, 2));
console.log(sum('1', '2'));
console.log(sum(['1'], ['2']));
console.log(sum(null, null));
函数的使用场景
我可以像其他对象那样使用函数,比如,可以将它们赋值给另一个变量,可以将将它们作为对象的属性值,可以作为参数传递给其他函数,还可以作为其他函数的返回值。
1.作为值被赋值给其他变量
function sayHi() {
console.log('Hi!');
}
sayHi();
var sayHi2 = sayHi;
sayHi2();
2.作为对象的属性值
function sayHi() {
console.log('Hi!');
}
var person = {
name: '张三',
greeting: sayHi
};
person.greeting();
3.作为参数传递给其他函数
var numbers = [1, 25, 18, 4, 7, 10, 2, 6];
var result2 = numbers.sort(function(a, b){
return a - b;
});
4.作为其他函数的返回值
function parent() {
function child() {
console.log('内部函数被调用了');
}
return child;
}
var fun = parent();
fun();
5.作为构造函数,用于创建自定义类型
根据函数的功能,我们将函数分成了两类:普通函数和构造函数。普通函数偏重于实现功能(业务逻辑),构造函数用来创建自定义类型的对象。
// 1. 当我们使用 new 操作符调用一个函数时,函数内部会自动创建一个该类型(Dog)的对象。
// 2. 我们可以在函数内部使用 this 关键字访问新创建的对象(例如,erha)
// 3. 函数调用结束时,新创建的对象会被自动的返回到函数外部。
// 构造函数,创建自定义类型
function Dog(dogName, dogAge) {
// 通过 this 关键字给新创建的对象添加属性
this.name = dogName;
this.age = dogAge;
}
// 创建 Dog 类型的对象
var erha = new Dog('哈士奇', 3);
var tugou = new Dog('中华田园犬', 6);
This的指向问题
var person = {
name: '李四',
sayName: function(){
console.log(person.name);
}
}
person.sayName();
在这个例子中,我们直接使用person.name,这种做法会增加方法与对象之间的耦合度(它们之间的依赖性变强了)。这样写是有问题的 ,如果我们的变量名修改了,我们必须同时修改方法中的变量名。幸运的是,JavaScript给我们提供了解决这个问题的方法。
javascript中的每一个作用域中都有一个this对象,它代表的是调用函数的对象。在全局作用域中,this代表的是全局对象(在web浏览器中指的是window)。如果包含this的函数是一个对象的方法,this指向的就是这个对象。因此在上面例子中就不用直接写对象的名字,而是使用this代替它,例如:
var human = {
name: '李四',
sayName: function(){
console.log(this.name);
}
}
human.sayName();
this通常是被自动赋值的,但是我们也可以改变this的指向。JavaScript给我们提供了 3 种函数方法来改变this的指向。
call()方法
这个方法的第一个参数表示this指向的对象,后面的所有参数都是函数的参数。例如:
function sayName(label) {
console.log(label+'--->'+this.name);
}
var name = '张三';
var person1 = {
name: '李四'
};
var person2 = {
name: '王五'
};
sayName.call(window,'global'); //'global--->张三'
sayName.call(person1,'person1'); //'person1--->李四'
sayName.call(person2,'person2'); //'person2--->王五'
apply()方法
這個方法和call方法的作用都是相同的,只不过在传递参数时候,call方法可以传递多个参数,而apply方法只能传递一个方法,并且要求是一个数组。
function sayName(label) {
console.log(label);
console.log(this.name);
}
var name = '张三';
var person1 = {
name: '李四'
};
var person2 = {
name: '王五'
};
sayName.apply(window,['global']); //'global--->张三'
sayName.apply(person1,['person1']); //'person1--->李四'
sayName.apply(person2,['person2']); //'person2--->王五'
bind()方法
bind()方法第一个参数是我们希望函数中this指向的对象,后面的参数是我们希望给函数的参数绑定的值。
var name = '张三';
var obj1 = {
name: '李四'
}
function sayName (age, gender) {
console.log(this.name, age, gender);
}
var newSayName = sayName.bind(obj1);
newSayName(); // '李四' undefined undefined
var newSayName2 = sayName.bind(obj1, 18);
newSayName2('女'); // '李四' 18 '女'
var newSayName3 = sayName.bind(obj1, 18, '女');
newSayName3(); // '李四' 18 '女'