本文共 19453 字,大约阅读时间需要 64 分钟。
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!点击上方蓝字关注我,我们一起学编程
欢迎小伙伴们分享、转载、私信、赞赏微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!语法
每条语句以 ;
结束,语句块用 {}
包裹。
JavaScript 不强制要求在每条语句结尾处添加
;
,浏览器的执行器会自动补齐。但为了安全起见,我们坚持在每条语句结尾处添加;
。
注释
单行注释://
/**/
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!Number
不区分整数与浮点数,统一用 Number 表示。
NaN
:Not A Number ,无法计算结果时用 NaN 表示。(0 / 0
)
Infinity
:无穷大,也表示超过 Number 所能表示的最大值。(1 / 0
) 字符串
使用 ''
或 ""
包裹的任意文本。
布尔值
布尔值只有 true
和 false
两种值,可以通过布尔运算 &&
、||
、!
与比较运算得到。
比较运算符
包括相等性比较与不等性比较。
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!JavaScript 中有两种相等性比较:
坚持只使用==
与===
。前者会自动进行类型转换后再比较;后者不会进行类型转换,只有类型一致时才会进行比较,否则直接返回 false 。===
比较运算符。
有一个例外,NaN
不与任何值相等。只能通过 isNaN()
函数判断。
null 与 undefined
null
表示“空”,与 0
和 ''
不同。undefined
表示“未定义”,在判断函数参数是否传递的情况下有用。
数组
同一个数组中可以包含多个类型的数据,并可以通过下标对数组中的元素进行读写。例如:[1, 2, 3, "Hello", null, true]
。另一种创建数组的方法是:var a = new Array(1, 2, 3);
。应坚持使用 []
创建数组。
对象
对象是一组由“键-值”组成的无序集合。例如:
var person = { name: "Mike", age: 20};
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!对象的键都是字符串类型,值可以是任意类型。可以通过对象名.属性名
的方式获取对象属性:perso.name
。
变量
变量名是大小写英文、数字、$ 和 _ 的组合,且不能用数字开头。在 JavaScript 中,同一个变量可以赋值多次,并且可以是不同的类型。
var a = 1;var $b = 'hello';a = true;
strict 模式
JavaScript 中不强制要求使用 var
声明变量,若变量没有使用 var
声明,就变成了全局变量,为了安全起见,坚持使用 var
对变量进行声明。
通过在 JavaScript 代码的第一行加上 use strict;
,进入 strict 模式,此时,任何变量的使用都强制使用 var
进行声明。
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!字符串
使用 ''
或 ""
包裹的任意文本都是字符串,单引号包括的字符串可以包含双引号,双引号包裹的字符串可以包含单引号。若想同时包含单引号与双引号,可以使用 \
进行转义。
模板字符串
可以使用 +
对字符串进行连接,例如:
var name = "Mike";var age = 20;var msg = name + "'s age is " + age;
当需要连接的字符串过多时,使用模板字符串会更加简洁。
var name = "Mike";var age = 20;var msg = "${name}'s age is ${age}";
操作字符串
var s = "Hello";/* length:获取字符串长度 */var len = s.length; // 5**微信搜索:编程笔记本。获取更多干货!****微信搜索:编程笔记本。获取更多干货!**/* toUpperCase:字符串转大写,原串不变 */var news = s.toUpperCase(); // "HELLO"/* toLowerCase:字符串转小写,原串不变 */var news = s.toLowerCase(); // "hello"/* indexOf:搜索指定字符串出现的位置,未找到返回 -1 */var pos = s.indexOf("lo"); // 3/* substring:返回指定索引区间的子串 */var news = s.substring(1,3); // "el"/* 省略结束下标 */var news = s.substring(2); // "llo"
length
数组可以包含任意数据类型,并可以通过下标访问数组元素。使用 length
获取数组长度。
var a = [1, 2, 3.14, 'Hello', null, true];var len = a.length; // 6
当直接对数组的 length
赋值时,会导致数组的长度变化。
var a = [1, 2, 3];a.length = 4; // [1, 2, 3, undefined]a.length = 2; // [1, 2]
当通过下标对数组元素进行访问时,如下标越界,则会引起数组扩容。
var a = [1, 2, 3];var e = a[1]; // 2a[5] = 'A'; // [1, 2, undefined, undefined, 'A']
在编写代码时,不建议直接修改数组的长度,使用下标时要确保下标不会越界。
indexOf()
搜素指定元素的位置。
var a = ['A', 'B', 'C'];var pos = a.indexOf('B'); // 1
slice()
截取数组的部分元素。
var a = [1, 2, 3, 4, 5];var b = a.slice(0, 4); // [1, 2, 3]/* 省略结束下标 */var b = a.slice(2); // [3, 4, 5]/* 省略始末下标 */var b = a.slice(); // [1, 2, 3, 4, 5]
push()
和 pop()
push 向数组尾部添加若干个元素,返回数组的新长度,pop 将数组的最后一个元素删除。
var a = [1];var len = a.push('A'); // 2var b = a; // [1, 'A']b.pop();var c = b; // [1]c.pop();/* 对空的数组进行pop()不会报错 */c.pop();var d = c // undefined
unshift()
和 shift()
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!unshift 向数组头部添加若干个元素,返回数组的新长度,shift 将数组的第一个元素删除。
var a = [1];var len = a.unshift('A'); // 2var b = a; // ['A', 1]b.shift();var c = b; // [1]c.shift();/* 对空的数组进行shift()不会报错 */c.shift();var d = c // undefined
sort
对数组进行排序。
var a = [2, 1, 3];a.sort();var b = a; // [1, 2, 3]
reverse
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!将数组中的元素颠倒次序。
var a = [2, 1, 3];a.reverse();var b = a; // [3, 1, 2]
splice
从指定的索引开始删除若干元素,然后再从该位置添加若干元素。
var a = [1, 2, 3, 4, 5];/* 从索引1开始删除2个元素,并在此位置添加新元素 */a.splice(1, 2, 'A', 'B', 'C'); // [1, 'A', 'B', 'C', 4, 5]/* 只删除不添加 */a.splice(2, 2); // [1, 2, 5]/* 只添加不删除 */a.splice(2, 0, 'A'); // [1, 2, 'A', 3, 4, 5]
concat
连接两个数组,并返回连接后的数组,原数组不变。
var a = [1, 2];var newa = a.concat([3, 4]); // [1, 2, 3, 4]var newa = a.concat(3, [4, 5]); // [1, 2, 3, 4, 5]
join
将当前数组的每个元素都用指定的字符串连接起来,然后返回连接后的字符串。
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!var a = [1, 2, 3];var s = a.join('-'); // "1-2-3"
多维数组
数组元素也是一个数组,通过多级下标访问。
var m = [[1, 2], ['A', 'B']];var e = m[1][1]; // 'B'
访问对象属性
对象是一种无序的集合数据类型,由若干个键-值对组成,以 {}
包裹。其中,键是字符串类型,值可以是任意类型。
var person = { name: "Mike", age: 20, city: "BeiJing"};
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!若属性名是一个合法的变量名,可以通过 对象名.属性名
访问对象的属性。
var name = person.name; // "Mike"
若属性名不是一个合法的变量名,仅仅是一个字符串的话,则在声明的时候,需要将属性名用引号包裹起来,访问时,使用 []
操作符。
var phone = { name: "Huawei", "phone-number": "12345"};var s = phone["phone-number"];
若访问一个不存在的属性,不会报错,返回 undefined
。
var person = { name: "Mike", age: 20, city: "BeiJing"};var h = person.height; // undefined
增删对象属性
对象是动态类型,你可以自由地给一个对象添加或删除属性。
var person = { name: "Mike"};/* 新增age属性 */person.age = 20;/* 删除name属性 */delete person.name;var p = person; // {age: 20}
属性的存在性
使用 in
判断对象是否含有某一属性。
var person = { name: "Mike", age: 20, city: "BeiJing"};var flag = "age" in person; // truevar flag = "height" in person; // false
对象也会拥有继承来的属性,若需要判断某个属性是否是某对象自身拥有的、而不是继承来的,使用 hasOwnProperty()
方法。
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!var person = { name: "Mike", age: 20, city: "BeiJing"};var flag = person.hasOwnProperty("name"); // true
使用 if () {...} else {...}
进行条件判断。
使用 for
、while
、do...while
进行循环控制,与 C 语言相同。
for in
使用 for in
可以很方便地对数组中元素的下标、对象中属性的键名进行遍历操作。
var a = [3.14, 2, 'hello', true];for (var elem in a) { console.log(elem); // 0, 1, 2, 3}var person = { name: "Mike", age: 20, city: "BeiJing"};for (var elem in person) { console.log(elem); // "name", "age", "city"}
既然能获取到下标,那么自然可以通过下标去访问对应的元素。
var a = [3.14, 2, 'hello', true];for (var idx in a) { console.log(a[idx]); // 3.14, 2, 'hello', true}
for of
使用 for of
可以直接对数组或对象中的元素进行遍历操作。
var a = [3.14, 2, 'hello', true];for (var elem of a) { console.log(elem); // 3.14, 2, 'hello', true}
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!前面说过,JavaScript 对象的属性名一定是字符串类型,其实使用 Number 类型作为属性名也十分合理。
Map
Map 是一组键-值对的结构,其底层数据结构为哈希表,具有极快的查找速度。使用 new Map()
生成一个 Map 。
var score = new Map([["Mike", 95], ["Tom", 100]]);score.get("Tom"); // 100
通常的做法是,初始化一个空的 Map ,然后动态改变其内容。
var score = new Map();/* 添加键值对 */score.set("Mike", 95);/* 判断是否含有某个键 */score.has("Mike"); // true/* 删除键值对 */score.delete("Mike");/* 获取键对应的值 */score.get("Mike"); // undefined
Set
Set 是一组键的集合,且不能重复。
var s1 = new Set([1, 2, 3]); // [1, 2, 3]s1.delete(2); // [1, 3]var s2 = new Set(); // []s.add(1); // [1]/* 自动过滤重复元素 */s.add(1); // [1]
循环操作除了前面说过的 for in
与 for of
外,还有一种 forEach
方法。它接受一个函数,每次迭代自动回调该函数。
var a = [1, 2, 3];/*element: 指向当前元素的值index: 指向当前索引array: 指向数组对象本身*/a.forEach(function(element, index, array) { console.log(array[index] + "==" + element); // 1==1 2==2 3==3});
Set 由于没有索引,因此回调函数的前两个参数都是元素本身。
var s = new Set(['A', 'B', 'C']);s.forEach(function (element, sameElement, set) { console.log(element); // 'A', 'B', 'C' console.log(sameElement); // 'A', 'B', 'C'});
Map 的回调函数参数与数组类似。
var score = new Map([["Mike", 95], ["Tom", 100]]);score.forEach(function(value, key, map) { console.log(map[key] + "==" + value); // 95==95 100==100});
如果对某些参数不感兴趣,可以忽略它们。
var a = [1, 2, 3];a.forEach(function(element) { console.log(element); // 1 2 3});
一个函数的定义:
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!function abs(x) { if (x >= 0) { return x; } else { return -x; }}
在上面的函数定义中:
function
指出这是一个函数定义;abs
是函数名称;(x)
括号中以 ,
为分隔列出函数参数;{...}
中为函数体,包含若干语句。函数执行到 return
后结束执行,并将结果返回。若没有 return
语句,函数执行完毕后返回 undefined
。
在 JavaScript 中,函数也是一个对象,因此我们可以像定义对象变量一样定义函数。
var abs = function (x) { if (x >= 0) { return x; } else { return -x; }};
在这种情况下,function (x) {...}
是一个匿名函数,将其赋值给变量 abs
后,就可以通过 abs
调用该函数了。
var n = abs(-5); // 5
JavaScript 中允许传入多余的参数,而不影响函数的调用,多传入的参数函数体并不需要,但也不会产生错误。
var n = abs(-5, "hello"); // 5
传入的参数比定义的参数少也不会报错,在上面的函数中,参数 x
将收到 undefined
,从而导致计算结果为 NaN
。
var n = abs(); // NaN
arguments
在函数中还有一个关键字 arguments
,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。类似但不是数组,它也有 length
属性,返回参数的个数。
function abs(x) { for (var elem of arguments) { console.log(elem); } if (x >= 0) { return x; } else { return -x; }}abs(1, 2, 3); // 1, 2, 3
rest
由于 JavaScript 允许传入多余的参数,我们有时候需要访问传入的多余的参数,有一个关键字 rest
可以解决这个问题。
function abs(x, ...rest) { for (var elem of arguments) { console.log(elem); } for (var elem of rest) { console.log(elem); } if (x >= 0) { return x; } else { return -x; }}**微信搜索:编程笔记本。获取更多干货!****微信搜索:编程笔记本。获取更多干货!**abs(1, 2, 3); // 1 2 3 // 2 3
若传入的参数比定义函数时的参数少,则 rest
为 []
,而非 undefined
。
在函数体内部使用 var
声明的变量,其作用域为整个函数体,在函数体外不能使用。
JavaScript 的函数定义有个特点,它会先扫描整个函数体的语句,把所有声明的变量**“提升”**到函数顶部。
function f() { var x = "Hello " + y; console.log(x); var y = "World"; console.log(x);}f(); // Hello undefined // Hello undefined
可以看到,在定义 x
时使用 y
并不会报错,原因是:扫描整个函数体后,“相当于”在函数体顶部有了 var x; var y;
声明语句。因此,在 x
中使用 y
是合法的,只是 y
的值尚未定义,显示为 undefined
。
若后面没有定义 y
,则会产生未定义的报错信息。
function f() { var x = "Hello " + y; console.log(x); // var y = "World"; console.log(x);}f(); // ReferenceError: y is not defined
由于 JavaScript 的这种“提升”行为,我们应该坚持在函数内部首先声明所有变量这一规则。
全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript 默认有一个全局对象 window
,全局变量实际上都成为了这个全局对象的一个属性,此时变量的定义不需要是使用 var
。
s = "Hello";console.log(s); // "Hello"console.log(window.s); // "Hello"
这样一来,我们可以合理猜测,在最外层的函数定义中,函数也是一个变量,当然也变成了 window
全局对象的一个属性了。
abs = function(x) { if (x >= 0) { return x; } else { return -x; }}; console.log(window.abs(-5)); // 5
这说明 JavaScript 实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报 ReferenceError
错误。
名字空间
因为全局变量会被绑定到 window
对象中,因此若不同文件中使用同名的全局变量就会产生命名冲突。一个有效的解决方案是:将当前文件的所有全局变量都绑定到一个全局对象中。
var MYSPACE = { };MYSPACE.size = 10;MYSPACE.abs = function(x) { if (x >= 0) { return x; } else { return -x; }};
块作用域
在函数内部使用 var
声明的变量会被“提升”,因此在整个函数内部都能引用。我们可以使用 let
声明一个拥有块作用域的变量。
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!function f() { for (var i = 0; i < 3; ++i) { console.log(i); } console.log(i);}f(); // 0 1 2 3
使用 let
:
function f() { for (let i = 0; i < 3; ++i) { console.log(i); } console.log(i);}f(); // Error: i is not defined
注释掉最后引用 i 的语句后:
function f() { for (let i = 0; i < 3; ++i) { console.log(i); } // console.log(i);}f(); // 0 1 2
常量
使用 const
定义一个常量,其拥有块作用域。
const PI = 3.14;PI = 3.1; // Error: Assignment to constant variable.
解构赋值
所谓解构赋值,就是同时对一组变量进行赋值。
下面是对数组元素进行解构赋值:
var [x, y, z] = ["I", "Love", "You"];var [x, [y, z]] = ["I", ["Love", "You"]];
解构赋值还可以忽略某些元素:
var [, , z] = ["I", "Love", "You"];console.log(z); // "You"
使用解构赋值可以从一个对象中取出若干个属性:
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!var person = { name: "Mike", age: 20, city: "Beijing"};var { name, age} = person;// name = "Mike"// age = 20
解构赋值也可以对嵌套的对象进行直接赋值。
var person = { name: "Mike", age: 20, city: "Beijing", brother: { his_name: "Tom", his_age: 18 }};var { name, age, brother: { his_name}} = person;// name = "Mike"// age = 20// his_name = "Tom"
使用解构赋值对对象属性进行赋值时,如果对应的属性不存在,变量将被赋值为 undefined
,这和引用一个不存在的属性是一致的。如果要使用的变量名和属性名不一致,可以用下面的语法获取:
var person = { name: "Mike", age: 20, city: "Beijing"};/* city不是变量,仅仅是为了让pos获得city属性 */var { name, height, city: pos} = person;// name = "Mike"// height = undefined// pos = "Beijing"
解构赋值还可以使用默认值,这样可以避免请求不存在的属性而返回 undefined
的问题。
var person = { name: "Mike", age: 20, city: "Beijing"};/* city不是变量,仅仅是为了让pos获得city属性 */var { name, height = 180, city: pos} = person;// name = "Mike"// height = 180// pos = "Beijing"
有一点需要注意,当变量是先声明再解构赋值的话,会有一些不同:
var person = { name: "Mike", age: 20, city: "Beijing"};var name, age;{ name, age} = person; // Error: Unexpected token '='
可以看到会报错,原因是:解释器将 {
开头的语句法当成了块处理,于是 =
不合法。一个可行的解决方法是将解构赋值语句用 ()
包裹起来。
var person = { name: "Mike", age: 20, city: "Beijing"};var name, age;({ name, age} = person);// name = "Mike"// age = 20
解构赋值的使用场景
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!在很多场合下,使用解构赋值会使得代码大大简化。
/* 交换两个变量的值 */var x = 1;var y = 2;[x, y] = [y, x];
在一个对象中绑定的函数,称为对象的方法。
var person = { name: "Mike", birth: 1994, age: function() { var x = new Date().getFullYear(); return x - this.birth; }};
在上面的例子中,我们为 person
对象添加了一个方法 age
,用于获取年龄。
值得注意的是,在该方法内部使用了 this
关键字。它是一个特殊的变量,始终指向当前对象。在调用 age
方法时,this
就指向 person
对象,所以可以获取到该对象的 birth
属性。
既然 this
始终指向当前对象,那么在全局环境,它就指向 window
(本例为非 strict 模式)。
s = "Hello";console.log(s); // "Hello"console.log(this.s); // "Hello"
有一点需要注意,若在对象方法中又定义了其他的函数,那么该函数内部的 this
指针指向 undefined
(非 strict 模式下指向 window)而非该对象本身了。
var person = { name: "Mike", birth: 1994, age: function () { function getAgeFromBirth() { var x = new Date().getFullYear(); return x - this.birth; } return getAgeFromBirth(); }};person.age(); // Error: Cannot read property 'birth' of undefined
从上面的报错信息可以看出,函数 getAgeFromBirth
中的 this
指向 undefined
。解决这个问题的一个可行的方法是:提前获取对象。
var person = { name: "Mike", birth: 1994, age: function () { /* 提前获取对象 */ var that = this; function getAgeFromBirth() { var x = new Date().getFullYear(); return x - that.birth; } return getAgeFromBirth(); }};console.log(person.age()); // 26
apply()
我们知道,在一个独立的函数中,this
的指向是不确定的:strict 模式下指向 undefined
,非 strict 模式下指向 window
。
我们通过函数本身的 apply
方法可以控制 this
的指向,它接收两个参数:第一个参数是需要绑定的 this
变量,第二个参数是表示函数本身参数的数组。
function getAge() { var x = new Date().getFullYear(); return x - this.birth;}var person = { name: "Mike", birth: 1994,};/* 将getAge函数绑定到person对象,并传递参数 */console.log(getAge.apply(person, [])); // 26
call()
call
的作用与 apply
相同,唯一的区别就是其参数传递不再是一个数组,而实将参数按顺序传入。
装饰器
利用 apply
,我们还可以动态改变函数的行为。JavaScript 的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
现在假定我们想统计一下代码一共调用了多少次 parseInt()
,除了手动去数之外,最佳方案是用我们自己的函数替换掉默认的 parseInt()
。
var count = 0;var oldParseInt = parseInt; // 保存原函数window.parseInt = function () { count += 1; return oldParseInt.apply(null, arguments); // 调用原函数};
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
一个简单的高阶函数:
function f(x) { return x;}function add(x, y, f) { return f(x) + f(y);}console.log(add(1, 2, f)); // 3
如果我们有一个数组 a = [1, 2, 3, 4, 5]
,我们希望对其中的每个元素都执行一次平方操作。这就可以用 map
来实现。
function pow(x) { return x * x;}var a = [1, 2, 3, 4, 5];var res = a.map(pow); // [1, 4, 9, 16, 25]
容易看到,map
就是一个高阶函数,它将运算规则抽象化成一个函数,我们可以在函数内指向任意复杂的运算。
数组的 reduce
方法把一个函数作用在这个数组上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算。
[x, y, z].reduce(f) == f(f(x, y), z)
一个简单的例子就是对数组的元素求和:
function add(x, y) { return x + y;}var a = [1, 2, 3];console.log(a.reduce(add)); // 6
使用 filter
将数组的某些元素过滤掉,返回剩余元素。
filter
接收一个函数,并将传入的函数依次作用于每个元素,然后根据返回值是 true
还是 false
决定是保留还是丢弃该元素。
下面的例子将数组中的偶数删除:
function f(x) { return x % 2 != 0;}var a = [1, 2, 3, 4, 5, 6];console.log(a.filter(f)); // [1, 3, 5]
filter
接受的回调函数可以有多个参数,但通常只使用第一个参数,表示数组的某个元素 element
。回调函数还可以接收另外两个参数,表示元素的位置 index
和数组本身 self
。
利用这些信息,我们可以很方便地对数组进行去重:
function f(element, index, self) { return self.indexOf(element) == index;}var a = [1, 1, 2, 2, 3, 4, 4, 5];console.log(a.filter(f)); // [1, 2, 3, 4, 5]
sort
用于数组的排序,可接受一个 cmp
函数。比较函数的逻辑是,对于传入的两个参数:若前者小于后者,返回 -1 ;若两者相等,返回 0 ;若前者大于后者,返回 1 。
function cmp(x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0;}var a = [1, 3, 5, 2, 4, 6];a.sort(cmp);console.log(a); // [1, 2, 3, 4, 5, 6]
上面是自定义 cmp
的用法,数组默认的排序方式是将所有元素转换成 String
后再排序。
var a = [1, 20, 10, 2];a.sort();console.log(a); // [1, 10, 2, 20]
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!除了 map()
、reduce()
、filter()
、sort()
这些方法可以传入一个函数外,Array 对象还提供了很多非常实用的高阶函数。
every
every
方法可以判断所有元素是否都满足测试条件。
function isLowerCase(x) { return x.toLowerCase() === x;}var a = ["Hello", "OK", "good"];console.log(a.every(isLowerCase)); // false;
find
find
方法用于查找符合条件的第一个元素。若找到则返回该元素,若未找到,则返回 undefined
。
/* 小写字符串 */function f1(x) { return x.toLowerCase() === x;}/* 大写字符串 */function f2(x) { return x.toUpperCase() === x;}var a = ["mike", "tom", "marry"];console.log(a.find(f1)); // "mike"console.log(a.find(f2)); // undefined
findIndex
findIndex
和 find
类似,也是查找符合条件的第一个元素,不同之处是:若找到则返回该元素的索引,若未找到,则返回 -1 。
/* 小写字符串 */function f1(x) { return x.toLowerCase() === x;}/* 大写字符串 */function f2(x) { return x.toUpperCase() === x;}var a = ["mike", "tom", "marry"];console.log(a.findIndex(f1)); // 0console.log(a.findIndex(f2)); // -1
forEach
forEach
和 map
类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach
常用于遍历数组,因此,传入的函数不需要返回值。
function f(x) { console.log(x);}var a = [1, 2, 3];a.forEach(f); // 1 2 3
高阶函数除了可以接收函数作为参数外,还可以将函数作为返回值。
一个对数组进行求和的例子:
function lazy_sum(arr) { var sum = function () { return arr.reduce(function (x, y) { return x + y; }); } return sum;}
当我们调用 lazy_sum
时,返回的并不是求和结果,而实求和函数,调用 f
时才真正进行求和计算。
var f = lazy_sum([1, 2, 3]);f(); // 6
在这个例子中,我们在函数 lazy_sum
中又定义了函数 sum
,并且,内部函数 sum
可以引用外部函数 lazy_sum
的参数和局部变量,当 lazy_sum
返回函数 sum
时,相关参数和变量都保存在返回的函数中,这就称为闭包(Closure)。
如下所示就是一个箭头函数:
x => x * x/* 等价于 */function(x) { return x * x;}
箭头函数相当于一个匿名函数,并且简化了函数的定义。除了上面的形式,箭头函数还有另外一种形式,这种形式的函数体可以包含多条语句:
x => { if (x > 0) { return x; } else { return -x; }}
当参数多余一个时,需要用 ()
包裹起来:
(x, y) => x * x + y * y;
虽然箭头函数类似于一个匿名函数,但有一点不同的是,箭头函数的 this
指向其外层调用者。
generator
(生成器)是 ES6 标准引入的新的数据类型。一个 generator
看上去像一个函数,但可以返回多次。
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!一个产生斐波那契数列的函数:
function fib(n) { var t, a = 0, b = 1, arr = [0, 1]; while (arr.length < n) { [a, b] = [b, a + b]; arr.push(b); } return arr;}
由于函数只能返回一次,所以必须将返回值设置为一个数组。
下面看 generator
如何完成这样的功能。
function* fib(n) { var t, a = 0, b = 1, cnt = 0; while (cnt < n) { yield a; [a, b] = [b, a + b]; cnt++; } return;}fib(5); // [object Generator]
可以看到,generator
由 function*
定义,并且,除了 return
语句,还可以用 yield
返回多次。
直接调用一个 generator
和调用函数不一样,fib(5)
仅仅是创建了一个 generator
对象,还没有去执行它。
有两种执行调用 generator
对象的方法。
generator
对象的 next()
方法:next()
方法会执行 generator
的代码,然后,每次遇到 yield
;就返回一个对象 {value: x, done: true/false}
,然后“暂停”。返回的 value
就是 yield
的返回值,done
表示这个 generator
是否已经执行结束了。如果 done
为 true
,则 value
就是 return
的返回值。当执行到 done
为 true
时,这个 generator
对象就已经全部执行完毕,不要再继续调用 next()
了。function* fib(n) { var t, a = 0, b = 1, cnt = 0; while (cnt < n) { yield a; [a, b] = [b, a + b]; cnt++; } return;}var f = fib(5);console.log(f.next()); // {value: 0, done: false}console.log(f.next()); // {value: 1, done: false}console.log(f.next()); // {value: 1, done: false}console.log(f.next()); // {value: 2, done: false}console.log(f.next()); // {value: 3, done: false}console.log(f.next()); // {value: undefined, done: true}
for ... of
循环迭代 generator
对象,这种方式不需要我们自己判断 done
.function* fib(n) { var t, a = 0, b = 1, cnt = 0; while (cnt < n) { yield a; [a, b] = [b, a + b]; cnt++; } return;}for (var x of fib(5)) { console.log(x); // 0, 1, 1, 2, 3}
微信搜索:编程笔记本。获取更多干货!
微信搜索:编程笔记本。获取更多干货!