今天和大家聊一下关于js函数容易被忽略的一些点,内容包含了标签函数、匿名函数、匿名自执行函数、递归函数、构造函数、闭包函数,尤其ES6语法之后,引入了 Class(类)这个概念,让js更接近传统面向对象语言的写法,需要大家掌握,希望对你更进一步了解js函数有所帮助。
1标签函数
标签函数本身就是一个常规函数,通过前缀到模板字面量来应用自定义行为,并且以这个模板字面量中的所有字符串组成的数组作为第一个参数,其余的所有${}
表达式作为剩下的第2,3,4……个参数:
定义两个字符串变量:
代码语言:javascript复制const name = '大潘',action = '点赞、分享';
定义一个(标签)函数:
代码语言:javascript复制function tag(arr, ...placeholder) {
console.log(arr);
console.log(placeholder);
}
tag`感谢关注${name},记得${action}哈!`;
打印结果:
这里,因为并不能确定模板字面量中的插值的数量,所以用剩余操作符把所有的插值组合为一个数组作为函数的第二个参数
所以,以上面为例,如果想要用标签函数在页面显示这样的一句话:“感谢关注大潘,记得点赞、分享哈!”,可以使用这段代码实现:
代码语言:javascript复制const name = '大潘',
action = '点赞、分享';
// 定义标签函数:
function tag(arr, ...placeholder) {
// 组成“感谢……哈!”这句话:
str = arr.reduce((prev, cur, i) => prev placeholder[i - 1] cur);
return str;
}
const result = tag`感谢关注${name},记得${action}哈!`;
// 写到页面上:
document.write(result);
进一步的,如果想要实现这样的效果:
“感谢关注大潘,记得点赞、分享哈!”
只需要把标签函数里reduce中的回调函数的返回值替换为prev `<span style="font-weight:700;color:red">${placeholder[i - 1]}</span>` cur
即可,这也是模板字面量的主要应用之一。
1匿名函数
常见的函数形式是这样的:
而对于匿名函数,形式是这样的:
如果你直接在编辑器里这样写,会报错,解决报错问题只需要在匿名函数的外部套一对小括号,这样就会把小括号里面的当作表达式来执行:
而你如果想要调用函数,只需要在其后边再加一对小括号,也就是我们下一个要讲的匿名自执行函数:
1匿名自执行函数
基本形式:(function(){})()
在聊匿名自执行函数之前,我们先看一下js里小括号“()”的作用,至少有两个:
- 调用函数;
- 把小括号里面的内容看作优先计算的表达式
那么你一定懂了为什么在匿名函数外边套一个小括号就不会报错,再在后边加一个小括号就会调用这个匿名函数的原理。
继续聊匿名自执行函数,顾名思义,就是在函数一创建出来就会立即执行。它相比于普通的函数,运行效率是一样的,但是差别在于匿名函数会在内部形成一个封闭的作用域,里面的变量不会对外部造成命名污染:
代码语言:javascript复制(function () {
var a = 123;
console.log('我是一个匿名函数', a);
})();
// 函数外部的变量:
var a = 456;
console.log(a);
两个a都能打印出来且不报错:
这在协同开发中很有用:比如,程序员小A和程序员小B协同开发一个项目,在不借助任何模块化工具的前提下,只要小A和小B都把自己的代码写到两个自执行的函数里,就可以完全避免命名冲突(两个人的代码互不干扰),不过缺点就是两个人的代码之前很难进行通信。
1递归函数
递归函数通常的形式是一个函数通过名称调用自己,比如:
代码语言:javascript复制function f(num) {
if (num <= 1) return num;
else {
return num * f(num - 1);
}
}
console.log(f(10));
这就是一个经典的递归阶乘函数。注意,在递归函数的函数体里面,必须定义一个停止递归的条件,这里的条件是if (num <= 1) return num
1构造函数
ES6语法中引入了 Class(类)这个概念,这样的JavaScript构造类的方式更接近传统的面向对象语言(比如 C 和 Java):
ES5中实例对象的传统方法:
代码语言:javascript复制function User(name, age) { //函数名的首字母通常大写
this.name = name;
this.age = age;
}
User.prototype.toString = function () {
return '我叫' this.name ',' '今年' this.age '岁了。';
};
var user = new User('dapan', 10000);
console.log(user); // User {name: 'dapan', age: 10000}
console.log(user.toString());// 我叫dapan,今年10000岁了。
此时如果我们打印user的类型:
代码语言:javascript复制console.log(typeof user);// Object
这里显示的并不是function类型,而是Object对象,因为你在创建user的时候在前边加上了new关键字,此时浏览器就会启动构造函数的执行流程,把user看作一个Object。
当你用这种方式创建一个构造函数时,会先在函数User里创建一个空的this对象:this = {}
,之后执行你自己编写的代码,最后返回这个this
对象。当然,在你创建和return
这个this
对象的时候,都是不可见的,是浏览器的默认行为。
ES6的写法:
constructor()
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor()
方法,如果没有显式定义,一个空的constructor()
方法会被默认添加。
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
toString(num) {
return '我叫' this.name ',' '今年' this.age '岁了。';
}
}
let user = new User('dapan', '10000');
console.log(user);// User {name: 'dapan', age: 10000}
console.log(user.toString());// 我叫dapan,今年10000岁了。
这里User
里面的constructor
和toString
方法都是储存在User
的prototype上,验证:
console.log(User.prototype);
打印结果:
对比ES6之前和之后的构造函数形式:
(之后我会专门出一期推文来讲ES6中Class 的基本语法)
1闭包函数
如果你在一个函数a中定义了一个局部变量x和另外一个函数b,并且return
函数b,再把函数a赋值给c,最后调用c,那么你觉得c函数是什么呢?实际上调用c其实就是调用了函数b,如下:
你会发现,我们在函数a的外部利用了其内部的函数b访问了局部变量x:
最后
参考:
https://es6.ruanyifeng.com/#docs/class
https://www.bilibili.com/video/BV1to4y1d7MM?spm_id_from=333.337.search-card.all.click&vd_source=2ebc8792eafcc474314f16a0ef600b74
https://www.bilibili.com/video/BV1UP4y1a7bk?spm_id_from=333.337.search-card.all.click&vd_source=2ebc8792eafcc474314f16a0ef600b74