函数是业务开发中最常见的。本篇一起讨论一下函数的变化。
原生js的函数
定义函数
如何定义函数,想必不用多说,如下2种方式:
function sum(a, b) {
return a + b;
}
sum(1, 2); // 3
var sum = function (a, b) {
return a + b;
}
sum(1, 2); // 3
函数调用
上述代码中已经提及一种调用方式,也是最常见的调用方式。
函数调用总共3种方式:
- 当事件发生时(当用户点击按钮时,函数绑定到dom上)
- 当 JavaScript 代码调用时(上述代码的方式)
- 自动的(自调用或自执行)
(function(a, b){
return a + b;
})(1, 2) // 3
arguments
arguments是JavaScript函数内置对象。它包含函数调用时使用的参数数组
function sum () {
var result = 0;
for (var i = 0; i < arguments.length; i++) {
result += arguments[i]
}
return result;
}
sum(1, 2); // 3
sum(1, 2, 3); // 6
使用arguments
后参数数量就可以不做限制了。
函数参数
关于参数注意2点:
- 基础类型的参数通过值传递
- 对象类型的参数通过引用传递的
function test (data) {
data = 10;
};
var a = 1;
test(a);
console.log(a); // 1
function test (data) {
data.key = 10;
};
var a = { key: 1 };
test(a);
console.log(a); // { key: 10 }
this
原生JS的this
在之前是必考问题,现在也会问到。
在js中this的指向是函数所有者,全局函数this指向window对象
,函数属于谁,this就指向谁,谁都不属于指向widnow
// html
<button onclick="test">测试<button>
// javascript
function test(){
console.log(this); // DOM对象
}
上一篇对象中关于super
关键字的分析,它和this
的区别:
super
指向当前对象的原型对象this
指向的是函数所有者super
只能用在对象的方法之中,用在其他地方都会报错。
call、apply、bind
call、apply、bind这三个算是JS的重点之一。
call
call
可以用来调用其他对象的方法。
官方例子:
var person = {
fullName: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"Bill",
lastName: "Gates"
}
person.fullName.call(person1, "Seattle", "USA");
使用call
方法调用person的fullName方法,注意此时this指向为call的第一个参数
apply
apply
方法与call
方法相似,区别在apply
的参数是数组方式
官方例子:
var person = {
fullName: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"John",
lastName: "Doe"
}
person.fullName.apply(person1, ["Oslo", "Norway"]);
使用apply
方法调用person的fullName方法,注意此时this指向为call的第一个参数
bind
关于bind的在W3C中并未找到,请查阅MDN的Function.prototype.bind()
bind
方法和call
方法类似,传参也一致,但是bind方法不会立即执行,需手动调用后才执行
var person = {
fullName: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"John",
lastName: "Doe"
}
person.fullName.bind(person1, ["Oslo", "Norway"])();
闭包
闭包大家都不陌生,在面试中经常会被提及的知识点。在W3C的JavaScript 闭包中由浅入深介绍了闭包,对闭包不熟悉的可以学习一下。
先看一下官方的例子
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();
add();
add();
add(); // 3
看完代码聊一下闭包的特点:
- 函数嵌套函数;
- 内部函数使用外部函数的参数和变量;
- 参数和变量不会被垃圾回收机制回收。
基于闭包的特点和代码分析,可以总结如下优点:
- 变量始终保持在内存中不会被销毁。
- 由于局部变量,防止污染全局变量
有优点也有缺点:
- 常驻内存,增加内存使用量。
- 使用不当会很容易造成内存泄露。
构造函数(对象构造器函数)
构造函数都不陌生,是创建对象时初始化对象,并且总与new运算符一起使用。
比如Object()
、Array()
等构造函数。除了内置的构造函数外,js允许自定义构造函数。
function Person() {
this.age = 18;
this.say = function () {
console.log("I'm " + this.name + ', my age is ' + this.age)
}
this.__proto__.hello = function () {
console.log("I'm " + this.name + ', my age is ' + this.age)
}
}
var man = new Person();
man.name = '张三';
man.say(); // I'm 张三, my age is 18
console.log(man); // Person{}
上述例子是个简单的构造函数,其中有say
和hello
方法,say
是显式添加方法,hello
是原型方法。
总结一下构造函数和普通函数的区别:
- 命名规范:构造函数首字母需大写。
- 调用方式:构造函数通过new 函数名()调用
- 返回值: 构造函数返回对象,普通函数需要return。
类型检测
像Object
、Array
等都能检测类型,那么自定义的构造函数如何进行类型检测呢?
利用前几章的知识来试验一下哪个方法可以成功
// typeof
console.log(typeof man); // object
// instanceof
console.log(man instanceof Person); // true
console.log(man instanceof Object); // true
// constructor
console.log(man.constructor === Person); // true
console.log(man.constructor === Object); // false
// getPrototypeOf
console.log(Object.getPrototypeOf(man) === Person.prototype); // true
console.log(Object.getPrototypeOf(man) === Object.prototype); // false
通过以上试验,constructor和getPrototypeOf可以检测出是否基于构造函数创建
ES6新增特性
参数:默认值、rest参数
原生js函数的参数不能设置默认值,只能采用变通的方法。ES6支持设置默认值,减少了代码复杂度。
// JS
function sumByJs(a, b) {
if (typeof a === 'undefined') a = 0;
if (typeof b === 'undefined') b = 0;
return a + b;
}
console.log(sumByJs()); // 0
// ES6
function sumByEs(a = 0, b = 0) {
return a + b;
}
console.log(sumByEs()); // 0
可以清晰的看到代码简洁很多。
在上面介绍了原生js的arguments
对象,在ES6中引入了rest
参数,用于获取函数的多余参数。
// JS
function sumByJs() {
console.log(arguments);
}
sumByJs(1, 2, 3, 4);
// ES6
function sumByEs(...num) {
console.log(num);
}
sumByEs(1, 2, 3, 4);
上述代码值相同,但是原生js的值属于arguments对象,ES6属于数组对象。ES6支持自定义参数名。
箭头函数
箭头函数使函数更加简洁,比如
// JS
function sumByJs(a, b) {
if (typeof a === 'undefined') a = 0;
if (typeof b === 'undefined') b = 0;
return a + b;
}
console.log(sumByJs(1, 2));
// ES6
const sumByEs = (a = 0, b = 0) => a + b;
console.log(sumByEs(1, 2));
箭头函数虽好,但也要注意:
- 箭头函数没有自己的
this
对象 - 不可以当作构造函数,不可以对箭头函数使用
new
命令,否则会抛出一个错误。 - 不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 - 不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数。
this指向问题
箭头函数的this
指向一直是个考点,无论Vue还是ES6都会有相应的问题。
// 作为全局函数
const a = () => {
console.log(this); // window
}
a()
// 作为对象的属性值
const b = {
a: () => {
console.log(this); // window
}
}
b.a();
// 在普通函数中
function c() {
const d = () => {
console.log(this); // window
}
d();
}
c();
// 在构造函数中
function D() {
const e = () => {
console.log(this); // D{}
}
e()
}
const d = new D();
上述四种场景打印了箭头函数的this指向,只有第四种创建实例后this才会指向实例,其他均指向window;
所以:箭头函数里面根本没有自己的this,而是引用外层的this
。
ES2019新增特性
toString()
ES10更新了toString
,返回函数代码本身,以前会省略注释和空格。
function a(a, b) {
// 返回a+b的值
return a + b;
}
console.log(a.toString())
在业务开发中很少用到这个方法,在调试中可能会遇到,了解即可。
catch 命令的参数省略
try {
throw new Error('异常')
} catch (err) {
console.log(err)
}
try {
throw new Error('异常')
} catch {
console.log('执行异常')
}
仅做了优化,实际业务中其实还是需要捕获实际的错误信息。
相关资料
- W3Chool的 JS 函数
- W3Chool的 JavaScript 函数
- W3Chool的 JavaScript 箭头函数
- W3Chool的 ECMAScript 函数概述
- MDN的 Function
- 阮一峰老师的 函数的扩展
相关试题
- 函数调用的3种方式是什么?
- 改变this指向的方式有哪些?
this
和super
有什么区别?- call、apply、bind方法的区别?
- 什么是构造函数?构造函数和普通函数有什么区别?
- 通过自定义构造函数创建的变量,如何检测它是这个构造函数的实例?
- 你知道闭包吗?它有什么特点?
- 简述一下闭包的优点和缺点?
- 下面代码如何打印出12345
for(var i=1; i<=5; i++) { setTimeout(function() { console.log( i ); }, 1000); }
- ES6对函数的参数做了哪些升级?
- ES6箭头函数和原生JS函数有什么区别?
- ES6箭头函数的
this
指向哪里? - ES6的箭头函数能用于构造函数吗?为什么?