JavaScript的函数的四种调用方式
要理解 JavaScript 中函数的四种调用模式,我们可以把函数想象成 “工具”,不同的调用方式就像 “工具的不同使用场景”,而 this 就是 “工具当前作用的对象”。
1. 方法调用模式
当函数是对象的一个属性时,这个函数就叫 “方法”。此时调用方法,this 就绑定到这个对象上。
举个例子:我们有个 “人” 对象 person,它有个 “属性” 是年龄 age,还有个 “方法” 是 grow(用来让年龄增长)。
var person = {
age: 18,
grow: function (num) {
// this 就是 person 对象
this.age += num || 1; // 没传num就默认涨1岁
}
};
person.grow(); // 调用方法,this绑定person
console.log(person.age); // 19
person.grow(2);
console.log(person.age); // 21
2. 函数调用模式
如果函数不是对象的属性,只是单独存在的,调用时 this 会绑定到全局对象(浏览器里就是 window)。这是 JavaScript 设计的一个小 “坑”。
举个例子:我们写了个普通函数 add,用来计算两个数的和。
function add(a, b) {
// 这里的this绑定到全局对象window
return a + b;
}
var sum = add(3, 4);
console.log(sum); // 7
如果在 “方法内部的小函数” 里用这种模式,就会出问题。比如:
var person = {
age: 18,
grow: function () {
// 方法里的this是person
function helper() {
// 但helper是“函数调用模式”,this变成全局对象了!
this.age += 1; // 这里实际改的是window.age,不是person.age
}
helper();
}
};
person.grow();
console.log(person.age); // 还是18,因为没改到自己
解决办法是先把 this 存到一个变量(比如 that)里,再在内部函数用这个变量:
var person = {
age: 18,
grow: function () {
var that = this; // 把person存到that里
function helper() {
that.age += 1; // 现在改的是person.age
}
helper();
}
};
person.grow();
console.log(person.age); // 19,成功了!
3. 构造器调用模式
JavaScript 是 “基于原型” 的语言,但它也模仿了 “类” 的语法。如果用 new 关键字调用函数,这个函数就成了 “构造器”,会创建一个新对象,且 this 绑定到这个新对象上。
举个例子:我们想创建很多 “学生” 对象,每个学生有name属性。就可以写一个构造器函数 Student:
function Student(name) {
// 用new调用时,this是新创建的对象
this.name = name;
}
// 创建新学生对象
var tom = new Student("Tom");
console.log(tom.name); // Tom
var lily = new Student("Lily");
console.log(lily.name); // Lily
注意:构造器函数一般首字母大写(比如 Student),提醒自己要用 new 调用。如果忘了加 new,this 会绑定到全局对象,就会把属性加到 window 上,容易出 bug。
4. Apply 调用模式
apply 是函数的一个 “工具方法”,它允许我们手动指定 this 的值,还能把参数放进一个数组里传递。
它接收两个参数:
- 第一个:要绑定给
this的对象。 - 第二个:参数数组。
举个例子:我们有个求和函数 add,但想让它临时给某个对象 “服务”,或者用数组传参:
function add(a, b) {
return a + b;
}
// 场景1:用数组传参
var nums = [5, 6];
var sum = add.apply(null, nums); // 第一个参数传null,this就用默认的全局对象
console.log(sum); // 11
// 场景2:手动指定this
var calculator = {
brand: "Casio"
};
// 让add函数的this绑定到calculator上(虽然这里没用到this,但演示了绑定)
sum = add.apply(calculator, [3, 4]);
console.log(sum); // 7
console.log(calculator.brand); // Casio(证明this绑定成功了)
再比如,我们可以 “借用” 其他对象的方法:
var dog = {
name: "Buddy",
bark: function () {
console.log(this.name + " 汪汪叫!");
}
};
var cat = {
name: "Kitty"
};
// 让cat“借用”dog的bark方法,同时this绑定到cat
dog.bark.apply(cat); // Kitty 汪汪叫!
参数
咱们可以把
arguments 理解成函数的 “万能参数收纳盒”。
当你调用函数时,不管传了多少个参数,这个 “收纳盒” 都会把它们全都装进去。哪怕函数定义时只写了一两个形式参数,多出来的参数也能在 arguments 里找到。
比如例子里的 sum 函数,它没定义具体要几个参数,但通过 arguments,它能把传进来的 4、8、15、16、23、42 一个个取出来相加,最后得到 108。
不过要注意,arguments 不是真正的数组,它只是长得像数组,有 length 属性,但没有数组的那些方法(比如 push、pop 之类的),这算是 JavaScript 设计上的一个小瑕疵。
我们可以把异常理解成程序里的 “意外状况”,比如函数期望接收数字,结果却收到了字符串。JavaScript 的异常处理机制就是用来 “应对这些意外” 的,避免程序因为小问题直接崩溃。
异常
1. 抛异常(throw):发现意外,主动报告
就像你写了个加法函数 add,要求必须传数字。如果传了非数字,就 “抛出” 一个异常,告诉程序 “这里出问题了”。
var add = function (a, b) {
// 检查a或b是不是数字
if (typeof a !== 'number' || typeof b !== 'number') {
// 抛出异常:类型错误,说明add需要数字
throw {
name: 'TypeError',
message: 'add needs numbers'
};
}
return a + b;
};
throw 会立刻中断函数执行,把这个 “异常对象”(包含 name 类型和 message 描述)抛出去。
2. 捕异常(try...catch):接住意外,妥善处理
如果直接调用 add("seven"),程序会崩溃。所以我们用 try...catch 来 “接住” 这个异常,让程序能优雅地处理问题。
javascript
运行
var try_it = function () {
try {
// 尝试执行这段代码,如果出异常,就跳去catch
add("seven"); // 这里传了字符串,会触发异常
} catch (e) {
// e就是捕获到的异常对象,打印它的类型和描述
document.writeln(e.name + ': ' + e.message); // 输出 TypeError: add needs numbers
}
};
try_it();
try块:包裹可能出异常的代码。catch (e)块:如果try里抛了异常,就会跳到这里,e就是那个异常对象,我们可以读取它的name和message来处理问题。
给类型添加方法
简单来说,throw 是 “报故障”,try...catch 是 “修故障” ,配合起来就能让程序在遇到意外时,不至于直接崩溃,而是能有针对性地处理问题~
我们可以把这段内容理解为 “给 JavaScript 的基础类型(如数字、字符串等)‘装新功能’”,就像给手机装 APP 一样,让它们能做更多事。下面分几块详细解释:
一、给类型加方法的原理:原型(prototype)
JavaScript 里的数字(Number)、字符串(String)这些基础类型,都有一个 “原型(prototype)” 结构。往这个原型上添加方法,所有该类型的实例都能直接用这个方法。
比如给 Function(函数的构造器)的原型加一个 method 方法,所有函数都能用上这个方法,不用每次都写 prototype 了:
Function.prototype.method = function (name, func) {
this.prototype[name] = func; // 给当前函数的原型加方法
return this;
};
二、实战:给数字加 integer 方法(智能取整)
JavaScript 本身的取整方法不太灵活,我们可以给数字自定义一个 integer 方法,让它能 “智能判断正负后取整”:
Number.method('integer', function () {
// this 就是当前的数字,判断正负后选对应的取整方法
return Math[this < 0 ? 'ceil' : 'floor'](this);
});
// 测试:-10/3≈-3.333,负数用ceil向上取整得到-3
document.writeln((-10 / 3).integer()); // 输出 -3
简单说,就是让数字自己能根据正负 “自动选最合适的取整方式”,不用我们手动判断了。
三、实战:给字符串加 trim 方法(去首尾空格)
JavaScript 原生没有 “去掉字符串首尾空格” 的方法,我们可以自己加一个:
String.method('trim', function () {
// 用正则表达式替换掉首尾的空格
return this.replace(/^\s+|\s+$/g, '');
});
// 测试:把" neat "首尾的空格去掉,变成"neat"
document.writeln('"' + " neat ".trim() + '"'); // 输出 "neat"
以后处理字符串空格时,直接调 字符串.trim() 就搞定了。
给类型增加方法就像 “给基础类型扩展新能力”,利用原型机制让所有实例都能用上新功能。同时要注意 “安全添加”,避免不同代码库之间的冲突~