在实际开发中,我们常常能够遇到的函数形式大概有四种情况,分别是函数声明,函数表达式,匿名函数与自执行函数。
相信大家对这几种常见的函数形式都已经不再陌生。但我还是要在这里专门总结一下关于函数的这些基础知识,以确保每一个读者都能够有足够的理论基础进行下一步的学习。
函数声明
函数声明是指利用关键字function来声明一个函数。
function fn() {
console.log('function');
}
在变量对象的创建过程中,function声明的函数比var声明的变量具有更加优先的执行顺序,即我们常会提到的函数声明提前。因此在同一执行上下文中,无论我们在什么地方声明了函数,我们都可以直接使用。
fn(); // function
function fn() {
console.log('function');
}
每一个函数都会产生一个自己的作用域,函数内部可以访问上层函数的变量对象。
function fn() {
var a = 20;
function bar() {
var b = 10;
return a + b;
}
return bar();
}
fn(); // 30
函数表达式
函数表达式其实是将一个函数体赋值给一个变量的过程。
var fn = function() {}
因此,我们理解函数表达式时,可以与变量共同理解。上面的代码其实执行步骤如下。
var fn = undefined;
fn = function() {}
其中var fn = undefined
则会因为变量对象的原因而提前执行,这就是我们常常提到的变量提升。因此当我们使用函数表达式时,就必须要考虑到代码的先后顺序,这是与函数声明不同的地方。
fn(); // TypeError: fn is not a function
var fn = function() {
console.log('function');
}
因此如果你比较喜欢使用函数表达式,那么你一定要有一个良好的编码习惯,已规避变量提升带来的负面影响。
函数体赋值的操作,我们还可以在其他很多场景能够遇到,如下。
function Person(name) {
this.name = name;
this.age = age;
// 在构造函数内部中添加方法
this.getAge = function() {
return this.age;
}
this.
}
// 给原型添加方法
Person.prototype.getName = function() {
return this.name;
}
// 在对象中添加方法
var a = {
m: 20,
getM: function() {
return this.m;
}
}
匿名函数
顾名思义,匿名函数就是没有名字的函数。通常匿名函数并没有使用一个变量来保存它的引用,我们通常会利用匿名函数作为一个参数,或者作为一个返回值。常见的大概有如下场景。
setTimeout中的参数。
var timer = setTimeout(function() {
console.log('延迟1000ms执行该匿名函数');
}, 1000);
数组方法中的参数。
var arr = [1, 2, 3];
arr.map(function(item) {
return item + 1;
})
arr.forEach(function(item) {
// do something
})
匿名函数作为一个返回值。
function add() {
var a = 10;
return function() {
return a + 20
}
}
add()();
在实际开发中,当匿名函数作为一个返回值时,为了方便调试,我们常常会将匿名函数添加一个名字。这样在chrome开发者工具中我们就能知道是它。
function add() {
var a = 10;
return function bar() {
return a + 20
}
}
add()();
在以往的经验中,许多同学会分不清匿名函数闭包。相信在学懂了前面几章的知识之后,你也会觉得这是一个很奇怪的现象。但是为了防止万一,这里也稍微提一下。匿名函数我们就当成函数来理解即可,而闭包的形成条件,仅仅只是有的时候会和匿名函数有关而已。
自执行函数
自执行函数其实也是匿名函数的一个非常重要的应用场景。因为函数会产生独立的作用域,因此我们常常会利用自执行函数来模拟块级作用域。并进一步在此基础上实现模块化的运用。
(function() {
// ...
})();
模块化的重要性需要反复强调,我希望读者能够在学习这些基础知识的过程中,慢慢养成对于模块化思维的一个认知与习惯。因此虽然前面在讲解闭包的章节中对于模块已经做了非常详细的分析,这里仍然要借助自执行函数来了解模块化。
一个模块可以包括:私有变量、私有方法、公有变量、公有方法。
当我们使用自执行函数创建一个模块,也就意味着,外界已经无法访问该模块内部的任何属性与方法。好在有闭包,作为模块之间能够相互交流的桥梁,让模块能够在我们自己的控制中,选择性地对外开发属性与方法。
(function() {
// 私有变量
var age = 20;
var name = 'Tom';
// 私有方法
function getName() {
return `your name is ` + name;
}
// 共有方法
function getAge() {
return age;
}
// 将引用保存在外部执行环境的变量中,这是一种简单的对外开发方法的方式
window.getAge = getAge;
})();
在前面的章节中我们已经创建过一个简单的状态管理模块。这里我们将扩展它,以便应对更加复杂的场景。
// 自执行创建模块
(function() {
// states 结构预览
// states = {
// a: 1,
// b: 2,
// m: 30,
// o: {}
// }
var states = {}; // 私有变量,用来存储状态与数据
// 判断数据类型
function type(elem) {
if(elem == null) {
return elem + '';
}
return toString.call(elem).replace(/[\[\]]/g, '').split(' ')[1].toLowerCase();
}
/**
* @Param name 属性名
* @Description 通过属性名获取保存在states中的值
*/
function get(name) {
return states[name] ? states[name] : '';
}
function getStates() {
return states;
}
/*
* @param options {object} 键值对
* @param target {object} 属性值为对象的属性,只在函数实现时递归中传入
* @desc 通过传入键值对的方式修改state树,使用方式与小程序的data或者react中的setStates类似
*/
function set(options, target) {
var keys = Object.keys(options);
var o = target ? target : states;
keys.map(function(item) {
if(typeof o[item] == 'undefined') {
o[item] = options[item];
}
else {
type(o[item]) == 'object' ? set(options[item], o[item]) : o[item] = options[item];
}
return item;
})
}
// 对外提供接口
window.get = get;
window.set = set;
window.getStates = getStates;
})()
// 具体使用如下
set({ a: 20 }); // 保存 属性a
set({ b: 100 }); // 保存属性b
set({ c: 10 }); // 保存属性c
// 保存属性o, 它的值为一个对象
set({
o: {
m: 10,
n: 20
}
})
// 修改对象o 的m值
set({
o: {
m: 1000
}
})
// 给对象o中增加一个c属性
set({
o: {
c: 100
}
})
console.log(getStates())