https://www.yuque.com/ofrqk7/ms7fd0/ccnueo https://www.yuque.com/ofrqk7/ms7fd0/afadgd
JavaScript入门
JavaScript简介
JavaScript是世界上最流行的脚本语言,因为你在电脑、手机、平板上浏览的所有的网页,以及无数基于HTML5的手机App,交互逻辑都是由JavaScript驱动的。 简单地说,JavaScript是一种运行在浏览器中的解释型的、弱类型的编程语言。 那么问题来了,为什么我们要学JavaScript?尤其是当你已经掌握了某些其他编程语言如Java、C++的情况下。 简单粗暴的回答就是:因为你没有选择。在Web世界里,只有JavaScript能跨平台、跨浏览器驱动网页,与用户交互。
JavaScript的基本特点
语法
声明变量
JS变量是松散型的,可以用来保存保存任何类型的数据
var onevar One //这里的定义是区分大小写的var x=2var person='Json'var message="Hello World"
打印变量
console.log(x)console.log(person)console.log(messang)
数据类型
基本类型
- 数值(Number)
- 布尔类型(Boolean)
- 字符型(String)
- 未定义(Undefined)
- 空值(Null)
- Symbol(ES6新引入的一种新的原始数据类型,表示独一无二的值)
① Number
整数和浮点数
var intNum1 = 1var floatNum2 = 11.1var floatNum3 = 0.1var floatNum4 = .1 //不推荐console.log(intNum1)console.log(floatNum2)console.log(floatNum3)console.log(floatNum4)
NAN:一个特殊的值,表示not a number,用来表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)
var x = NaN/10console.log(x)
②Boolean
ture和false,注意大小写
var test = 'Hello World'Boolean(test)var a = undefinedBoolean(a)var b = 0Boolean(b)
③String
var str1 = 'hello'var str2 = "world" //单引号'',双引号""和反引号``都行//模板字符串var str = `我是${str1}`console.log(str)
字符串操作:
indexOf():查找字符串,用来定位第一次出现指定值的索引
var str = 'Today is a happy day'str.indexOf('is')str.indexOf('it')
replace():替换字符
var str = 'Today is a happy day'var a = str.replace("happy","sad")console.log(a)
concat():连接两个字符串
var str1 = 'Hello'var str2 = 'World'var str3 = str1.concat(str2)
includes():查找是否包含某个子字符串
var str = 'Today is a happy day'str.includes('happy')
④Undefined
表示没有初始化的变量
var testconsole.log(test == undefined)
⑤Null
表空值,一个完全空的对象
Object.prototype.__proto__//原型链的尽头
引用类型
- 对象(Object)
- 数组(Array)
- 函数(Function)
①Object
```javascript //创建方法有两种 var obj = new Object() obj.name = “Jack” obj.age = 80
var obj = { name : ‘Jack’, age : 80 }
//取出对象的方法也有两种 console.log(obj.name) console.log(obj[‘age’])
<a name="cY4bm"></a>### ②Array> 数组是数据的有向序列。但是数组的每一项都可以保存任何类型的数据。数组的大小是可以动态调整的,即可以随着数据的添加自动增长以容纳新增数据。在读取和设置数组的值时,要使用方括号并提供相应值的基于 0 的数字索引```javascriptvar arr = [1,'1',true,[0,1,2,3]]arr[0] //1arr[1] //'1'arr[2] //truearr[3] //[0,1,2,3]arr.length //4,获取数组的长度
判断数组,数组的遍历
//判断数组使用isArray函数var arr = [1,'1',true,[0,1,2,3]]Array.isArray(arr)//数组的遍历//for循环for(var i = 0;i<=arr.length;i++){console.log(arr[i])}//forof遍历for(var item of arr){console.log(arr[item])}//foreach遍历arr.forEach((item,index)=>{console.log(item)})
push()和unshift():
var arr = ['Jack','Lucy','Mark']arr.push('Sam') //在最后插入arr.unshift('Sam') //在最前面插入
迭代的方法:
- every:对数组中的每一项运行给定函数,如果该函数对每一项都返回 true ,则返回 true
- filter() :对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
- forEach() :对数组中的每一项运行给定函数。这个方法没有返回值。
- map() :对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
- some() :对数组中的每一项运行给定函数,如果该函数对任一项返回 true ,则返回 true 。 ```javascript var arr1 = [1,2,3,4,5] var arr2 = [2,4,6,8]
function test(item , index , arr){ return (item%2) == 0 }
arr1.every(test) arr2.every(test) arr1.some(test)
arr1.filter(test)
<a name="LwG2j"></a>### ③Function```javascript//函数声明语法function test(){}//函数表达式的形式var test = function(){}//函数返回值function test(){return 1;}
数据类型的判断
var x = 1var isTrue = falsevar str = '今天也是****的一天呢'var untypeof x //numbertypeof isTrue //booleantypeof str //stringtypeof un //undefined
数据类型的转换
5 + null // 返回 5 null 转换为 0"5" + null // 返回"5null" null 转换为 "null""5" + 1 // 返回 "51" 1 转换为 "1""5" - 1 // 返回 4 "5" 转换为 5
基础语法
操作符
//算数运算符//+ - * / ++ --var x=1//递增x++++x//递减同上//赋值运算符x += 1 //相当于x=x+1//比较运算符//== === != !== > < >= <=//逻辑运算符//&& || !//条件运算符//?:
==和===的区别
==表示等于,===表示绝对等于,即类型和值均相等
&&和||的使用
//特殊用法//当||左边为false时,才会执行右边的代码var test = 1test === 2 || console.log('||')//当&&左边为true时,才会执行右边的代码test ===1 && console.log("&&")
条件语句
//ifif(){}else if(){}else{}//switchswitch(n){case 1:执行代码块 1break;case 2:执行代码块 2break;default:与 case 1 和 case 2 不同时执行的代码}
循环语句
//forfor(var i=0;i<10;i++){console.log(i)}//whilewhile(i<10){i++}//do-whiledo{console.log(i)i++}while(i<10)
变量,作用域和内存问题
栈内存和堆内存

栈内存:存放的是局部变量和全局变量(基本数据类型 Number,String,Boolean,Undefined,Null)。 堆内存:存放的是数组和对象(引用数据类型 Object)
复制变量
基本类型的复制需要开辟一个新值,直接将变量赋值给它

引用类型的复制其实是复制地址,共用一个地址里面的变量

let a=12;let b=a;b=13;console.log(a)let n={name:'teal'};let m=n;m.name='red';console.log(n.name);
作用域
在某个空间范围内,可以对数据进行读写操作
全局作用域
//var t=123console.log(t)//隐式全局作用域function a(){ty = 321}a()console.log(ty)
函数作用域
//函数作用域//function lut(){var z = 100}lut()console.log(z) //z is not defined//function bar(){var m = 50function foo(){console.log(m) //50}foo()}
块级作用域
for(var i=0;i<5;i++){console.log(i)}//i污染到了全局var foo = true;if(foo){var bar = foo*2;//污染到全局console.log(bar)}console.log(bar)//块作用域本不应该污染全局//在Es6中会使用到let和const来定义变量
作用域链
一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。 但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成链条就叫做作用域链。
变量提升
// 变量提升//变量和函数在内的所有声明都会在任何代码被执行前首先被处理(只有声明会被提升,而赋值被停留在原地)/** 所以这个过程可以看成* var a*console.log(a)*a = 1*/console.log(a)var a = 1
LHS查询和RHS查询0
LHS查找变量函数名,RHS查找值
JS深入
变量定义
在 JS 中我们可以使用以下三种方式来定义一个变量,我们推荐使用前面两个
let(变量,不能重复定义)const(常量,在定义时必须赋值const a = 1)var
- “var” 没有块级作用域
块级作用域就是包含在{…}中的作用域
if (true) {var test = true; // 用 "var" 而不是 "let"}console.log(test); // true,变量在 if 结束后仍存在
“var” 在函数开头被处理
function sayHi() {phrase = "Hello";console.log(phrase);var phrase;}sayHi();
…它完全等同于这个(
var phrase被上提到函数开头):function sayHi() {var phrase;phrase = "Hello";console.log(phrase);}sayHi();
可能你们不觉得有什么,但是你们看下面这段代码,这完全就和你的代码表现的不一致了
function sayHi() {phrase = "Hello";console.log(phrase);if (false) {var phrase; // var 没有块级作用域}}sayHi();
其实 var 的危险不只有这些,这里只举了两个比较清楚好讲,其他区别可以看 ES6 系列之 let 和 const
什么时候使用 let 或者 const
我们建议优先使用 const,然后在使用 const 出现错误的时候再使用 let
注意:在这之后我们讲述都讲使用 let 和 const,来进行变量的声明,以避免不必要的麻烦
类型转换
- 字符串转换
- 显式的类型转换 String(value) ```javascript let value = true; console.log(typeof value); // boolean
value = String(value); // 现在,值是一个字符串形式的 “true” console.log(value, typeof value); // string
- 隐式的类型转换 **value + ''**```javascriptlet value = true;console.log(typeof value); // booleanvalue = value + ''; // 现在,值是一个字符串形式的 "true"console.log(value, typeof value); // string
- 数字型转换
- 显式的类型转换 Number(value) 和 parseInt(string[, redix]) ```javascript const str = “123”; console.log(typeof str); // string
const num = Number(str); // 变成 number 类型 123 console.log(typeof num); // number
- 隐式的类型转换 **"6" / "2"**```javascriptconsole.log( '6' / '2' ); // 3, string 类型的值被自动转换成 number 类型后进行计算
加号 ‘+’ 连接字符串
几乎所有的算术运算符都将值转换为数字进行运算,加号+是个例外。如果其中一个运算元是字符串,则另一个也会被转换为字符串。console.log(1 + '2'); // '12' (字符串在加号右边)console.log('1' + 2); // '12' (字符串在加号左边)
这仅仅发生在至少其中一方为字符串的情况下。否则值会被转换为数字。
console.log(true + 2); // 3
布尔型转换
- 显式的类型转换 Boolean(value) ```javascript console.log(Boolean(1)); // true console.log(Boolean(0)); // false
console.log(Boolean(‘hello’)); // true console.log(Boolean(‘0’)); // true console.log(Boolean(‘ ‘)); // true (中间是空格) console.log(Boolean(‘’)); // false
- 隐式的类型转换 **if(value)** 和 ...```javascriptif ('hello') {console.log('world');}
| 值 | 变成…… |
|---|---|
0, null, undefined, NaN, '' |
false |
| 其他值 | true |
函数
这些是上次课讲到的创建函数的方法
// 函数声明function test(){}// 函数表达式const test = function(){};// 函数返回值function test(){return 1;}
函数声明和函数表达式有什么区别?
- 使用函数声明可使它的调用先于声明
当 JavaScript 准备运行脚本或代码块时,它首先在其中查找函数声明并创建函数
在处理完所有函数声明后,其他代码才被执行。所以我们能够在定义他之前使用这些函数 ```javascript sayHi(“John”); // Hello, John
function sayHi(name) {
console.log( Hello, ${name} );
}
- 而使用表达式声明就会出现这样的问题```javascriptsayHi("John"); // error!const sayHi = function(name) {console.log( `Hello, ${name}` );};
箭头函数
例如我们有这样的一个表达式函数
const sum = function(a, b) {return a + b;}console.log(sum(1, 2))
事实上我们拥有一个简单并且减少很多麻烦的函数定义方式箭头函数
const sum = (a, b) => {return a + b;}console.log(sum(1, 2))
…我们还可以更加精简,在函数只有一行的时候我们可以将 {} 和 return 去掉
const sum = (a, b) => a + b;console.log(sum(1, 2))
注意:为了简便后面函数函数定义我们都使用箭头函数
默认值
const showMessage = (from, text = "没有给信息") => {console.log( from + ": " + text );}showMessage("Ann"); // Ann: 没有给信息
不定数量的参数
你们应该注意到了,我们的调试大法 console.log(string1, string2, ...) 我们可以传许多的参数进去,难道 JS 在定义这个函数的时候要定义很多个参数吗?肯定不需要,我们可以使用下面的方式来接受不定数量的参数
const _consoleLog = (string, ...args) => {console.log(string, args);}_consoleLog('a', 'b', 'c', 'd', 'e'); // 'a' [ 'b', 'c', 'd', 'e' ]
对象
可以说除了基本数据以外的所有东西都是对象类型
我们之前复习的说过这样的一句话,在 JavaScript 中,对象深入到这门语言的方方面面。所以在我们深入理解这门语言之前,必须先理解对象。
在上节课我们已经知道了怎么去定义对象,这里我们可以将对象想象成存放文件的橱柜。文件按照他们的名字来排列。这样根据文件名(变量名)我们就很容易找到、添加或删除一个文件(变量)了。
对象只是带有属性和方法的特殊数据类型。
- 创建
我们可以用下面两种语法的任一种来创建一个空的对象(“空柜子”): - new
字面量表示
const user = new Object();const user = {}; // “字面量” 的语法
通常,我们用花括号(后者)。这种方式我们叫做字面量。
事实上我们可以在创建的时候就立即给对象一些属性,在 {...} 里面放置一些键值对。
const user = { // 一个对象name: "John", // 键 "name",值 "John"age: 30, // 键 "age",值 30 (相当于: age: age)};
const age = 30const nameStr = 'name'const user = { // 一个对象[nameStr]: "John", // 键 "name",值 "John"age, // 键 "age",值 30 (相当于: age: age)};
属性有键(或者也可以叫做名字,标识符),在冒号的左边":",值在冒号的右边。
在 user 对象中, 有两个属性:
- 第一个的键是
"name"值是"John"。 - 第二个的键是
"age"值是30。
读取属性
单个读取
// 读取文件的属性console.log(user.name); // Johnconsole.log(user['age']); // 30 方括号中是可以放变量的
循环遍历读取
for(key in user) {// key 会是 user 中的键值console.log(user[key]); // 会执行两遍,返回 John 和 30}
添加属性
属性的值可以是任意类型,让我们加个布尔类型:user.isAdmin = true;// user['isAdmin'] = true; 也是可行的
删除属性
delete user.age;
引用复制
这个上节课是讲了的,我们不知道大家理解没有。
对象类型与基本类型的变量有很大的区别原始类型:字符串,数字,布尔类型 – 是会复制一份再进行赋值的
const message = "Hello!";const phrase = message;
他们在内存中是两个不同的东西
- 引用类型
现在我们有了两个变量,但是都指向同一个对象,就像我们的一个抽屉带有两把钥匙,如果一个钥匙(admin)去使用了抽屉,稍后使用另外一个钥匙(user)打开的时候,就会看到有变化。const user = { name: 'John' };const admin = user;admin.name = 'Pete';console.log(user.name); // Pete
复制
那我们怎么解决这个问题,能否像原始类型那样在赋值的时候先复制一次答案是可以的
这里只介绍一种简单的浅拷贝的方法,深拷贝你们后面会知道的。const user1 = Object.assign({}, user);
- 浅拷贝 ```javascript const user = { name: “John”, sizes: { height: 182, width: 50 } };
const clone = Object.assign({}, user); clone.name = ‘hgk’; console.log(user.name, clone.name); console.log(user.sizes === clone.sizes); // true,同一个对象
// user 和 clone 共享 sizes 对象 user.sizes.width++; // 在这里改变一个属性的值 console.log(user.sizes.width, clone.sizes.width); // 51
---<a name="10dc0e5c"></a>## [垃圾回收](https://zh.javascript.info/garbage-collection)对于开发者来说,JavaScript 的内存管理是自动的、无形的。我们创建的原始值、对象、函数……这一切都会占用内存。当某个东西我们不再需要时会发生什么?JavaScript 引擎如何发现它、清理它?<a name="sUm9m"></a>### 可达性JavaScript 中主要的内存管理概念是**可达性**。简而言之,**『可达』**值是那些以某种方式可访问或可用的值。它们保证存储在内存中。1. 这里列出固有的可达值基本集合,这些值明显不能被释放。<br />比方说:- 当前函数的**局部变量**和**参数**。- 嵌套调用时,当前调用链上**所有函数的变量与参数**- **全局变量**- (还有一些内部的)这些值被称作**根**。2. 如果一个值从**根**值访问到,则认为这个值是可达的。<br />比方说,如果局部变量中有一个对象,并且该对象具有引用另一个对象的,则该对象被认为是可达的。而且它引用的内容也是可达的。在 JavaScript 引擎中有一个被称作垃圾回收器(GC)的东西在后台执行。它监控着所有对象的状态,并删除掉那些已经不可达的<a name="gfrMq"></a>### 一个简单的例子```javascript// user 引用了这个对象const user = {name: "John"};
这里的箭头描述了一个对象引用。全局变量 "user" 引用了对象 {name:"John"}
如果 user 的值被覆盖了,这个引用就没了:
user = null;
大概把可达的概念讲清楚了,其他类容请看标题对应的教程。
对象方法与 “this”
在代码中我们使用频率最高的应该是,我们的调试大法console.log('hello')这样形式来执行方法,你们应该猜到了 console 是一个预定义的对象,而上面有 log 这个方法,但是我们之前只学会怎样去定义属性,那么怎样给对象定义方法?
对象通常被用来表示真实世界中的实体,比如用户等等:
const user = {name: "John",age: 30};
另外,在现实世界中,用户可以操作:从购物车中挑选某物、登录、注销等。
在 JavaScript 中,操作是通过属性中的函数来实现。
我们类比于对象讲述方式来说,我们怎样去执行、添加或删除一个对象方法。
- 创建 ```javascript const user = { name: “John”, age: 30, sayHi: function() { // 可以发现我们这里使用类似于“函数表达式”的方式来创建一个函数 console.log(“Hello”); } }; user.sayHi(); // Hello!
// 方法简写看起来更好,对吧? const user = { name: “John”, age: 30, sayHi() { // 与 “sayHi: function()” 一样 console.log(“Hello”); } }; user.sayHi(); // Hello!
- **添加**```javascriptconst user = {name: "John",age: 30};user.sayHi = function() { // 请注意我们这里使用的是 “函数表达式” 来创建一个函数console.log("Hello!");};user.sayHi(); // Hello!
注意:上面这两种创建函数的方法并不是在所有情况中表现是一样的
- 删除
const user = {name: "John",age: 30,sayHi() {console.log("Hello");}};delete user.sayHiuser.sayHi(); // TypeError: user.sayHi is not a function
方法中的 “this”
这时可能你会有疑问了❓ 我们怎么在对象方法中去访问到对象本身❓
相信你肯定想到了,比如上面的 user 定义之后应该在全局作用域中,我们无论在哪里都应该能访问到,在对象方法中也不例外。
const user = {name: "John",age: 30,sayHi() {console.log(`我是:${user.name}`);}};user.sayHi(); // "John"
…但是,这又是挖的一个坑,不是为啥章节的标题是 this 呀?事实上这样的代码是不可靠的。如果我们将 user 复制给另一个变量。例如 admin = user,并赋另外的值给 user,那么它将访问到错误的对象。
如下所示:
let user = { // **** 这里我们使用了 let ******name: "John",age: 30,sayHi() {console.log(`我是:${user.name}`); // 导致错误}};const boss = {name: 'boss'}const admin = user; // 用户翻身当管理员user = boss; // 我们居然让 boss 去当 user 了admin.sayHi(); // 噢哟!在 sayHi() 使用了旧的变量名。叫成了 boss
那我们怎么才能,方便的在对象的方法中访问对象本身?对,就是我们的标题 this
const user = {name: "John",age: 30,sayHi() {console.log(`我是:${this.name}`);}};user.sayHi(); // "John"
let user = { // **** 这里我们使用了 let ******name: "John",age: 30,sayHi() {console.log(`我是:${this.name}`);}};const boss = {name: 'boss'}const admin = user; // 用户翻身当管理员user = boss; // 我们居然让 boss 去当 user 了admin.sayHi(); // 噢哟!不错呦,叫对了。
请注意:它的值在普通情况(不是箭头函数)下并不取决于方法声明的位置,而是(取决)于在『点之前』的是什么对象。
const user = { name: "John" };const admin = { name: "Admin" };const sayHi = function() {console.log(`我是:${this.name}`);}// 在两个对象中使用的是相同的函数user.f = sayHi;admin.f = sayHi;// 它们调用时有不同的 this 值。// 函数内部的 "this" 是 ***点之前*** 的这个对象。user.f(); // John (this == user)admin.f(); // Admin (this == admin)admin['f'](); // Admin(使用点或方括号语法来访问这个方法,都没有关系。)
基本类型的方法
你可能有这样的疑问,String 又不是对象为什么他能像对象方法那样去使用❓
const str = 'Hello';console.log(str.toUpperCase()); // HELLO
以下是 str.toUpperCase() 实际发生的情况:
- 字符串
str是一个基本类型。所以在访问它的属性时,会即刻创建一个包含字符串字面值的特殊对象,并且具有很多有用的内置方法,例如toUpperCase()。 - 该方法运行并返回一个新的字符串(由
console.log显示)。 - 特殊对象被销毁,只留下基本类型
str。
构造函数和操作符 “new”
常规的 {...} 语法允许创建一个对象。但是我们经常需要创建许多类似的对象,例如多个用户或菜单项等等。
而你又不知道用户名字,你需要一个工厂或者说叫做模板的东西帮你创建一些差不多的对象,而这个就叫做构造函数。
构造函数
构造函数在技术上是常规函数。不过有两个约定:
- 他们首先用大写字母命名。
- 它们只能用
"new"操作符来执行。 ```javascript function User(name) { this.name = name; this.isAdmin = false; this.sayHi = function() { console.log( “My name is: “ + this.name ); }; }
const user = new User(“Jack”);
console.log(user.name); // Jack console.log(user.isAdmin); // false user.sayHi()
当一个函数作为 `new User(...)`执行时,它执行以下步骤:1. 一个新的空对象被**创建并分配**给 `this`。2. 函数体执行。通常它会修改 `this`,为其**添加新的属性**。3. **返回** `this` 的值。换句话说,`new User(...)` 做类似的事情:```javascriptfunction User(name) {// this = {};(隐式创建)// 添加属性到 thisthis.name = name;this.isAdmin = false;// return this;(隐式返回)}
所以 new User("Jack") 的结果是相同的对象:
const user = {name: "Jack",isAdmin: false};
现在,如果我们想创建其他用户,我们可以调用 new User("Ann"),new User("Alice") 等等。比每次使用字面量创建要短得多,而且易于阅读。
解构赋值
先看一个愚蠢的例子。。。。
const arr = "Ilya Kantor".split(' ');const firstName = arr[0];const surname = arr[1];console.log(firstName); // Ilyaconsole.log(surname); // Kantor
数组解构
再看一个智者应该有的操作
const arr = "Ilya Kantor".split(' ');// 解构赋值const [firstName, surname] = arr;console.log(firstName); // Ilyaconsole.log(surname); // Kantor
甚至他还有一些其他技巧
忽略一部分
const [ , , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];// title:Consul
剩余的 ‘…’ ```javascript const [name1, name2, …rest] = [“Julius”, “Caesar”, “Consul”, “of the Roman Republic”];
console.log(name1); // Julius console.log(name2); // Caesar
console.log(rest[0]); // Consul console.log(rest[1]); // of the Roman Republic console.log(rest.length); // 2
- **默认值**```javascript// 默认值const [name = "Guest", surname = "Anonymous"] = ["Julius"];console.log(name); // Julius (来自数组的值)console.log(surname); // Anonymous (默认值被使用了)
对象解构
当然除了数组,解构赋值同样适用于对象。
基本的语法是:
const {var1, var2} = {var1:…, var2:…}
在等号右侧有一个已经存在的对象,我们想把它拆开到变量中。等号左侧的“对象”包含了右侧对象相应属性的“模板”
const options = {title: "Menu",width: 100,height: 200};const {width, title, height} = options; // 甚至不需要满足顺序,只需要满足对应的属性就可以了console.log(title); // Menuconsole.log(width); // 100console.log(height); // 200
对象解构同样包含了 剩余的 ‘…’ 和 默认值 请大家自己尝试,或者看标题中的教程
嵌套解构
const options = {size: {width: 100,height: 200},items: ["Cake", "Donut"],extra: true // 一些不会被解构的额外属性};// 为了清晰起见,解构赋值语句被写成多行const {size: { // 把 size 赋值到这里width,height},items: [item1, item2], // 把 items 赋值到这里title = "Menu" // 在对象中不存在的属性(会使用默认值)} = options;console.log(title); // Menuconsole.log(width); // 100console.log(height); // 200console.log(item1); // Cakeconsole.log(item2); // Donut
递归
当一个函数解决一个任务时,在该过程中它可以调用很多其它函数。那么当一个函数调用自身时,就称其为递归。
例子
我们写一个函数 pow(x, n),它可以计算 x 的 n 次方,即用 x 乘以自身 n 次。
pow(2, 2) = 4pow(2, 3) = 8pow(2, 4) = 16
有两种实现方式。
迭代思路:
for循环:const pow = (x, n) => {let result = 1;// 在循环中用 x 乘以 resultfor (let i = 0; i < n; i++) {result *= x;}return result;}console.log(pow(2, 3)); // 8
递归思路:简化任务,调用自身:
const pow = (x, n) => {if (n == 1) {return x;} else {return x * pow(x, n - 1);}}console.log(pow(2, 3)); // 8
比如,为了计算
pow(2, 3),递归变体经过了下面几个步骤:pow(2, 3) = 2 * pow(2, 2)pow(2, 2) = 2 * pow(2, 1)pow(2, 1) = 2

