博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【JavaScript】JS入门教程
阅读量:3927 次
发布时间:2019-05-23

本文共 19453 字,大约阅读时间需要 64 分钟。

微信搜索:编程笔记本。获取更多干货!

微信搜索:编程笔记本。获取更多干货!

点击上方蓝字关注我,我们一起学编程

欢迎小伙伴们分享、转载、私信、赞赏

JavaScript 学习笔记

文章目录

1. 快速入门

微信搜索:编程笔记本。获取更多干货!

微信搜索:编程笔记本。获取更多干货!

1.1 基本语法

语法

每条语句以 ; 结束,语句块用 {} 包裹。

JavaScript 不强制要求在每条语句结尾处添加 ; ,浏览器的执行器会自动补齐。但为了安全起见,我们坚持在每条语句结尾处添加 ;

注释

单行注释://

块注释:/**/

微信搜索:编程笔记本。获取更多干货!

微信搜索:编程笔记本。获取更多干货!

1.2 数据类型和变量

Number

不区分整数与浮点数,统一用 Number 表示。

NaN:Not A Number ,无法计算结果时用 NaN 表示。(0 / 0

Infinity:无穷大,也表示超过 Number 所能表示的最大值。(1 / 0

字符串

使用 ''"" 包裹的任意文本。

布尔值

布尔值只有 truefalse 两种值,可以通过布尔运算 &&||! 与比较运算得到。

比较运算符

包括相等性比较与不等性比较。

微信搜索:编程笔记本。获取更多干货!

微信搜索:编程笔记本。获取更多干货!

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 进行声明。

1.3 字符串

微信搜索:编程笔记本。获取更多干货!

微信搜索:编程笔记本。获取更多干货!

字符串

使用 ''"" 包裹的任意文本都是字符串,单引号包括的字符串可以包含双引号,双引号包裹的字符串可以包含单引号。若想同时包含单引号与双引号,可以使用 \ 进行转义。

模板字符串

可以使用 + 对字符串进行连接,例如:

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"

1.4 数组

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'

1.5 对象

访问对象属性

对象是一种无序的集合数据类型,由若干个键-值对组成,以 {} 包裹。其中,键是字符串类型,值可以是任意类型。

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

1.6 条件判断

使用 if () {...} else {...} 进行条件判断。

1.7 循环

使用 forwhiledo...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}

1.8 Map 和 Set

微信搜索:编程笔记本。获取更多干货!

微信搜索:编程笔记本。获取更多干货!

前面说过,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]

1.9 iterable

循环操作除了前面说过的 for infor 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});

2. 函数

2.1 函数定义与调用

一个函数的定义:

微信搜索:编程笔记本。获取更多干货!

微信搜索:编程笔记本。获取更多干货!

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

2.2 变量作用域与解构赋值

在函数体内部使用 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];

2.3 方法

在一个对象中绑定的函数,称为对象的方法。

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); // 调用原函数};

2.4 高阶函数

微信搜索:编程笔记本。获取更多干货!

微信搜索:编程笔记本。获取更多干货!

一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

一个简单的高阶函数:

function f(x) {
return x;}function add(x, y, f) {
return f(x) + f(y);}console.log(add(1, 2, f)); // 3
2.4.1 map

如果我们有一个数组 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 就是一个高阶函数,它将运算规则抽象化成一个函数,我们可以在函数内指向任意复杂的运算。

2.4.2 reduce

数组的 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
2.4.3 filter

使用 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]
2.4.4 sort

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]
2.4.5 Array

微信搜索:编程笔记本。获取更多干货!

微信搜索:编程笔记本。获取更多干货!

除了 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

findIndexfind类似,也是查找符合条件的第一个元素,不同之处是:若找到则返回该元素的索引,若未找到,则返回 -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

forEachmap 类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach 常用于遍历数组,因此,传入的函数不需要返回值。

function f(x) {
console.log(x);}var a = [1, 2, 3];a.forEach(f); // 1 2 3

2.5 闭包

高阶函数除了可以接收函数作为参数外,还可以将函数作为返回值。

一个对数组进行求和的例子:

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)。

2.6 箭头函数

如下所示就是一个箭头函数:

x => x * x/* 等价于 */function(x) {
return x * x;}

箭头函数相当于一个匿名函数,并且简化了函数的定义。除了上面的形式,箭头函数还有另外一种形式,这种形式的函数体可以包含多条语句:

x => {
if (x > 0) {
return x; } else {
return -x; }}

当参数多余一个时,需要用 () 包裹起来:

(x, y) => x * x + y * y;

虽然箭头函数类似于一个匿名函数,但有一点不同的是,箭头函数的 this 指向其外层调用者。

2.7 generator

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]

可以看到,generatorfunction* 定义,并且,除了 return 语句,还可以用 yield 返回多次。

直接调用一个 generator 和调用函数不一样,fib(5) 仅仅是创建了一个 generator 对象,还没有去执行它。

有两种执行调用 generator 对象的方法。

  • 一种是不断地调用 generator 对象的 next() 方法:next() 方法会执行 generator 的代码,然后,每次遇到 yield ;就返回一个对象 {value: x, done: true/false} ,然后“暂停”。返回的 value 就是 yield 的返回值,done 表示这个 generator 是否已经执行结束了。如果 donetrue ,则 value 就是 return 的返回值。当执行到 donetrue 时,这个 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}

微信搜索:编程笔记本。获取更多干货!

微信搜索:编程笔记本。获取更多干货!

你可能感兴趣的文章
Firefox 18周岁
查看>>
IdentityServer4系列 | 初识基础知识点
查看>>
诊断日志知多少 | DiagnosticSource 在.NET上的应用
查看>>
Chrome正在启用HTTP/3,支持IETF QUIC
查看>>
Net5 已经来临,让我来送你一个成功
查看>>
System.Text.Json中时间格式化
查看>>
怎么将SVG转成PNG(.NET工具包编写)
查看>>
为什么曾经优秀的人突然变得平庸?
查看>>
.NET 5 中的隐藏特性
查看>>
.NET5都来了,你还不知道怎么部署到linux?最全部署方案,总有一款适合你
查看>>
我画着图,FluentAPI 她自己就生成了
查看>>
BenchmarkDotNet v0.12x新增功能
查看>>
使用 .NET 5 体验大数据和机器学习
查看>>
C# 中的数字分隔符 _
查看>>
使用 docker 构建分布式调用链跟踪框架skywalking
查看>>
Github Actions 中 Service Container 的使用
查看>>
别在.NET死忠粉面前黑.NET5,它未来可期!
查看>>
Winform 进度条弹窗和任务控制
查看>>
部署Dotnet Core应用到Kubernetes(二)
查看>>
持续交付二:为什么需要多个环境
查看>>