参考B站视频:千锋前端JavaScript全套教程_JS零基础完美入门到项目实战

Javascript入门篇

前言

网页三剑客

HTML、CSS、JS称为网页基础,HTML和CSS是一种标记语言,通过标识符号去说明需要的功能,主要运用在网页中展示内容和定义样式,而JS是一种编程语言,需要自己去拆分功能逻辑,通过代码实现需要的功能,具有逻辑性,需要有编程思维和分析问题、解决问题的能力!

  • HTML是内容和结构,相当于身体
  • CSS是外观样式和布局,相当于衣服装饰
  • javaScript是活动,相当于灵魂和动作

javascript组成

由三部分组成BOM、DOM、ECMAScript,主要作用是用ECMAScript语法去使用BOM和DOM!

  • ECMAScript:JavaScript的语法标准。包括变量、表达式、运算符、函数、if语句、for语句等。
  • DOM:文档对象模型,操作网页上的元素的属性和方法,比如让盒子移动、变色、轮播图等。
  • BOM:浏览器对象模型,操作浏览器部分功能的属性和方法,比如让浏览器自动滚动。

JavaScript的核心作用

JS是以事件驱动为核心的一门语言,通过页面中的元素,或者浏览器等去触发事件,来动态的生成内容,变化页面等,使页面具有一种交互性,动态性!

事件三要素——事件源、事件、事件驱动程序

通常我们写好的HTML和CSS网页就是静态的网页,是下载到浏览器解析出来的图文,不会产生任何变化,但是JS语言可以让这些元素成为一个隐藏在草中的“夹子”,当你触碰到这个元素时,就可以发生一件事,比如变化这个元素中的内容,比如动画等!

事件源是编写的网页中的元素,事件是规定好的,JS编程主要是写事件驱动程序,也就是发生什么事

比如,我用手去按开关,灯亮了。

- 事件源是:开关
- 事件是:按开关。
- 事件驱动程序是:灯的亮与灭。
再比如,网页上弹出一个广告,我点击右上角的`X`,广告就关闭了。

- 事件源是:`X`。
- 事件是:onclick。
- 事件驱动程序是:广告关闭了。

于是我们可以总结出:谁引发的后续事件,谁就是事件源,比如浏览器对象BOM,元素对象DOM!

  • 常见事件:

    • 事件源:引发后续事件的html标签。

    • 事件:js已经定义好了(见下图)。

    • 事件驱动程序:对样式和html的操作,也就是DOM。

img

  • 常见的代码步骤:
  1. 通过DOM对象获取事件源:document.getElementById(“box”);

  2. 绑定事件: 事件源.事件= function(){ 事件驱动程序 };

box.onclick = function(){};
  1. 书写事件驱动程序:也就是函数,function里面的内容,一般是关于DOM的操作

JS基础

JavaScript书写位置

行内式、内嵌式、外链式

行内式

是直接把代码书写在标签内,分为a标签和非a标签的书写格式

  • a标签书,写在href属性上,需要导入JavaScript包
<a href="javasript: alert('hello world');">点我试试</a>
//在浏览器弹出一个提示框,alert();
  • 非a标签,写在属性上,添加行为属性
<a href="#" onclick="alert('hello world');">点我试试</a>

内嵌式

是把放在标签对内

<sript>
代码
</script>

外链式

把代码书写在.js文件内,在HTML通过引入,src属性专门引入

<sript src=".js文件路径"></script>
一般放在body最后

编程概述

JS属于编程语言,程序本质上,有两大板块,一个是数据,一个是函数!

数据可以理解成要操作的基本单位,如同原料,数字、字符串、布尔、数组、对象等!函数可以理解成输入、处理、输出!如同工厂,输入参数,处理可以是算法,输出就是函数返回值!

一、数据结构

JavaScript变量

  • 关键字 var或let,准备一个容器

  • 书写方式 var 名字 = 数据;

  • 一个变量只能存储一个值,当再次赋值值,会替换内容!具有动态属性!

  • 变量名称是标识符

注释

  • 单行 //

  • 多行 /* */

打印

一种方法,已经被定义好的方法,是BOM对象或者DOM对象里的方法,直接使用!

alert('打印内容');浏览器弹出框

console.log('打印内容');控制台

document.write('打印内容');页面中

1.数据类型

基本数据类型

  • 数值Number:十进制,科学计数法,其它进制0x 0o

  • 字符串String:一切以单引号’ ‘或双引号“ ”包裹的内容

  • 布尔Boolean:ture或false两个值,其中undefind、null、””、0、NaN、false都是假值;

  • Null:赋值为null;

  • 未定义Undefined:变量未赋值;

类型检测

运算后得到的值,进行不知道类型,进行类型检测

- 使用 typeof 关键字来进行数据检测,运算结果是该变量的数据类型

    - `typeof 待检测的值`

类型转换

​ 处理数据值,数据类型不对,比如当输入框提交的数据为文本,想转化为数字进行运算!

转数值

把其它类型值,转化为数值!

  1. Number:Number(); 括号内待转值,结果为转换的数值,如果可以转,结果就是数字,不可以则是NaN
  2. parseInt:parseInt();括号内待转值,结果为转换的数值,同理!当数字和文本结合时,一个一个字符读取转换,当碰到可以转换时就是数字,不可以就停止,最终结果就是可以转换的数字!
  3. parseFloat:parseFloat();与上同理;能解析到小数部分!

转字符串:同理

  1. String:String();括号内待转值,结果为转换的字符串
  2. toString:.toString();前面填待转值,括号内不用填,结果为转换的字符串

转布尔:同类

  1. Boolean:Boolean()括号内待转值,结果为转换的布尔值!除6种false,其它都是true!

运算符

同类数据类型存在运算,不同数据类型也可以存在运算,运算的结构还是其中一种数据类型!

算术运算符:+ - * / %

+:加法运算

  • 当两边都是数字,两边都是布尔,进行数学运算
  • 当任意一边是字符串,进行字符串拼接,结果是个字符串

其它:- * / %只能进行数学运算!

赋值运算符:= += -= *= /= %=

先运算再赋值,=赋值操作,把右边的数据,赋值给左边的变量!

关系运算符:比大小、是否相等

大小关系运算 > < >= <=、等值运算== === != !==,结果一定是布尔值

  • > < >= <=
  • == === != !==:两个等号,不考虑数据类型,只看值,三个等号,要考虑数据类型!

逻辑运算符:&& || !与、或、非(取反),单目

自增自减运算:++ --单目运算

  • 前置并参与运算时,先自增(减),再参与其它运算
  • 后置并参与运算时,先参与其它运算,再自增(减)

条件运算符:布尔表达式?值1:值2三目运算,运算结果为值1或者值2的结果!

a!='undefined' ? a=a : a=0;
a = a>1 ? a++ : a--;

引用数据类型

二、函数

1.函数的结构

分支结构

默认代码是从上到下依次执行,一条路走到黑,而分支相当于代码在执行中遇到的岔路,选择是否执行这段程序,或者该执行哪一段程序!先执行判断条件,根据条件的结果,执行结果为真的一段代码

if语句

if(){}

根据()内的布尔值,决定是否执行接着的内容!为真时,执行包裹的代码块!

  • ()里面写的是条件,一般是逻辑运算、关系运算,运算的结果只有真或假两种情况;
  • {}里面写的是当条件为真时执行的代码块,一般是处理命令,算法步骤;

if(){}else{}

根据()内的布尔值,true时执行if后的{};false时执行与之相邻的else后的{}!

  • 衍生if(){}else if(){}

哪个条件满足就执行哪个,前面执行了,后面就不执行了

if(){

}else
if(){

}
  • 衍生if(){}else if(){}else{}

依次判断,都不满足,执行最后else的代码!

if(){

}else
if(){

}else{

}
  • 可以嵌套使用!

案例:判断平年,还是闰年

//可以被4整除的,且不能被100整除,或者能被400整除的都是闰年!
//条件:(year%4===0 && year%100!==0) || year%400===0
//执行语句:year+'是闰年' 否则 year+'不是闰年'
let year = 2078;
if(year%4===0 && year%100!==0 || year%400===0){
console.log(year+'是闰年');
}else{
console.log(year+'不是闰年');
}

switch语句

switch(){case 比较值1: case 比较值2: }

根据()内的已知条件,与case后的值进行比较,完全匹配后,执行对应的代码!也可以改写成if第3种模式if(XX===OO){}else if(XX===PP){},本质执行的就是if语句

switch(已知条件){
case 比较值1:
代码块1
case 比较值2:
代码块2
}

使用break;

结束语句,跳出选择框!如果没有break;且其中某个值匹配,后面的语句直接穿透执行!即忽视后面case的值,直接执行case对应的语句!从第一个满足条件的位置向下穿透,直到出现break!

switch(已知条件){
case 比较值1:

break;
case 比较值2:

break;
}

使用default:

在所有选项都不符合时,执行默认语句,相当于if语句中最后的else!

switch(已知条件){
case 比较值1:

break;
case 比较值2:

break;
default:

}

案例,几月几日是这一年的第几天?

//通过加法计算,几月几日中,每一个【月的天数】相加,最后加上单独的【日的天数】!
//利用switch的穿透,先计算几个月的天数相加,最后加上日!
//case有几个月 totalDays=totalDays+当月天数,如果是1月,则月份上需要加0天,直接计算日的天数,如果是2月,则过去的的1个月天数相加,如果是12月,则是过去的前11个月天数相加,例如"12月5日=11月天数+10月天数+9月天数....1月天数+最后加上5日"所以先写12,向下穿透执行每月相加!
case 12: totalDays=totalDays+30;
case 11: totalDays=totalDays+31;
case 10: totalDays=totalDays+30;
case 9: totalDays=totalDays+31;
case 8: totalDays=totalDays+31;
case 7: totalDays=totalDays+30;
case 6: totalDays=totalDays+31;
case 5: totalDays=totalDays+30;
case 4: totalDays=totalDays+31;
case 3: totalDays=totalDays+30;
case 2: totalDays=totalDays+31;
//再最后,判断平年和闰年,给2月加上不同的天数!
if(year%4===0 && year%100!==0 || year%400===0){
totalDays=totalDays+29;
}else{
totalDays=totalDays+28;
}
//最后totalDays=totalDays+日的天数
totalDays=totalDays+day;
//定义年月日三个变量!分别赋值!

循环结构

while循环语句

while(){}

()中为真,则执行{}内的代码块,依次执行结束后,再返回判断()中是否为真,不断这样重复!也就意味着使用while()循环时,当执行到()为真时,则开始循环;为假时,则结束循环;如果()为真,且()一直为真不改变,则是死循环!

注意

()的布尔值与{}中的语句无关时,也就意味着()里面的值不会被改变时,其结果要么为真,则一直执行,要么为假,则从不执行,显然这两种状态都不是想要的,一般放一个待改变的值!

通常在()放入变量,一开始时为真,执行{}中的语句,并改变这个变量的值,当执行到想要的次数后,也就是()结果为假时,从而跳出循环!也就意味着()内一般是关于变量的关系运算、逻辑运算

while语句三步走

第一步:定义控制变量初始值多少

第二步:设置条件判断

第三步:执行语句中改变初始值步数多少,

这三步共同来控制循环次数,且控制变量是呈规律的变化!利用规律的步长值参与其它运算!

定义初始值;
while(初始值>n){
执行语句1
执行语句2
......
改变初始值;
}

break

终止上一层循环!

案例:求10的阶乘!1*2*3*4*5*6*7*8*9*10

//积=积*(变量+1)
let i = 1;
let number=1;
while(i<11){
number =number * (i+1);
i++;
}
//10! 10*9*8*7*6*5*4*3*2*1
//最左积=最左积*(控制变量);利用有规律变化的控制变量,从9变化到1,简写成total *=控制变量;
total=total*i;
//控制变量初始值为9
let total=10;
let i=total-1;
//改变初始值
i--;
//条件判断来控制变量改变到多少,改变到最小为1就行!
(i>0)

do-while循环语句

do{}while();一般情况可以与while(){}可以互换,不同的是,do{}while();是先执行{}中的语句,再判断(){}中的语句至少执行一次,适合连贯的代码节奏!

案例:在浏览器弹出个问题和输入框prompt(),回答正确,结束;回答错误,继续回答!

//浏览器弹出输入框prompt(),(中为提示内容,可以显示在框上面!)
//输入的内容,就是prompt()的运算结果!
//分析,首先弹出输入框,回答问题,判断输入的文本,是否为设置的结果,不是,就继续弹出!
let answer;
do{
answer = prompt('你是不是狗?');
}while(answer !== '是');
alert('真的狗');

for循环语句

for(){}

while()一样,定义变量;条件判断;改变初始值,标准的循环三步骤,再加上要循环的代码!

执行顺序与while()一致,先定义变量,再条件判断,再重复执行的代码,最后才是改变初始值,再然后条件判断,再重复执行的代码,再改变初始值,再条件判断……

  • for()中的小括号,把(定义变量;条件判断;改变初始值)三要素包裹在一起,{}放重复执行的代码!
  • 执行顺序与while()一致,优势是避免忘记设置改变初始值,避免死循环!
  • 可以嵌套使用!
for(定义变量;条件判断;改变初始值){
重复执行的代码;
}

案例:在控制台输出1~100,所有3的倍数!

//能整除3,余数为0
//拿到1~100的所有数字,这里借助初始值变量,按步长变化,得到1~100个数!
for(let i=1;i<=100;i++)
//是3的倍数就输出,否则什么也不做
if(i%3===0){
console.log(i);
}
最终程序
for(let i=1;i<=100;i++){
if(i%3===0){
console.log(i);
}
}
//使用while()改写
let i = 1;
while(i<=100){
if(i%3===0){
console.log(i);
}
i++;
}

循环嵌套练习

案例:页面中打印9行9列*,呈现为正方形

//在页面中输出一个*号
document.write('*');
//在页面中输出一行9个*号,执行9次document.write('*');
//还可以用&nbsp来调整间隔,使用h5标签
for(let i=0;i<9;i++){
document.write('*&nbsp&nbsp&nbsp');
}
//在页面中输出一行9个*号,再输出一行9个*号...直到9行,把一行的执行9次,每次后换行标签!
for(let j=0;j<9;j++){
for(let i=0;i<9;i++){
document.write('*');
}
document.write('<br/>');
}
//改写为while()
let j=0; //定义外层初始变量
while(j<=9){
let i=0; //定义内层初始变量
while(i<=9){
document.write('*&nbsp&nbsp&nbsp');
i++; //改变初始变量
}
document.write('<br/>');
j++;
}
//本质和for一致,先在循环体前定义变量,紧接着就是判断条件,然后是循环语句,最后是改变变量!

案例:页面中打印9行9列*,呈现为三角形

//第一行1个*,第二行2个*...,第九行9个*
//第一行
for(i=1;i<=1;i++){
document.write('*&nbsp&nbsp&nbsp');
}
document.write('<br/>');
//第二行
for(i=1;i<=2;i++){
document.write('*&nbsp&nbsp&nbsp');
}
document.write('<br/>');
//再用一个循环,来控制判断条件中的小于等于变量j,每循环一次,执行j++;
for(let j=1;j<=9;j++){

}
//最终代码如下,内层循环打印*,外层控制行,内层控制列!一行1列,2行2列,用外层变量控制内层次数,改变内层循环的判断条件,外层循环既实现循环9次,又用初始变量实现控制内存的循环次数!
for(let j=1;j<=9;j++){
for(i=1;i<=j;i++){
document.write('*&nbsp&nbsp&nbsp');
}
document.write('<br/>');
}

循环嵌套的妙用

for(){for(){}}

当两层循环嵌套,外层循环的初始变量,作为内层循环的判断条件时!

外层循环,通过初始变量,实现控制内层循环的循环次数,通过外层循环变量的步长,来控制内存循环次数呈规律重复次数!

  • 内层for()的条件判断,i<=j是由两个变量构成,共同来控制循环次数,重点是右侧的j是变化的,也就意味着,内层的循环次数,也是由外层初始变量控制!即第1次执行外层,内层循环1次;第2次执行外层,内层循环2次;第3次执行外层,内层循环3次;
  • 到最后,外层执行一次,内层执行9次!外层执行一次,内层执行j次!
//使用while()实现
let j = 1;
while(j<=9){
let i = 1;
while(i<=j){
document.write('*&nbsp&nbsp&nbsp');
i++;
}
document.write('<br/>');
j++;
}

案例:页面打印九九乘法表

//利用上方程序,可以控制每行排列,再用循环控制输出,显示的内容!
//第一个内容为1*1=1,三个变量,这三个变量,其中两个变量和循环的初始变量一致!再设个结果变量
document.write('i');
document.write('*');
document.write('j');
document.write('=');
document.write('sum');
//或者用`+`连接字符和变量!
document.write(i+'*'+j+'='+sum);
//总体代码
let sum;
for(let j=1;j<=9;j++){
for(i=1;i<=j;i++){
sum=i*j;
document.write(i+'*'+j+'='+sum+'&nbsp&nbsp&nbsp');
}
document.write('<br/>');
}

案例:寻找最大公约数!输入框

//公约数,就是同时被两个数字整除,余为0,关系为且
//最大,也就是众多公约数中,最大的那个公约数,存在范围是,小于两个数较小那个,大于1
//输入两个数,判断大小
let x = prompt('第一个数');
let y = prompt('第二个数');
//用循环来一个数一个数试,范围是从1开始,直到两个数较小那个,用三目判断大小!
for(let i=1;i<=(x>y?y:x);i++)

//最终程序,从1到其中较小一个数,依次验证
let x = prompt('第一个数');
let y = prompt('第二个数');
let j;
for(let i=1;i<=(x>y?y:x);i++){
if(x%i===0&&y%i===0){
j=i;
}
}
document.write(j);

//最终程序2,从大到小的验证,第一个满足条件的就是最大的!更方便,从新改写,用break终止循环!
let x = prompt('第一个数');
let y = prompt('第二个数');
for(let i=(x>y?y:x);i>=1;i--){
if(x%i===0&&y%i===0){
document.write(i);
break;
}
}

案例:寻找最小公倍数!

//公倍数:能同时整除两个数,余为0,关系且,最小就是众多公倍数最小那个!
i%m===0 && i%n===0
//设置m、n两个变量,公倍数范围是大于两个数,至无限!最小公倍数范围是较大数至m*n内,利用for的初始变量,来遍历这个范围,利用if判断结果,利用break提前退出循环!找到最合适遍历范围!
//最终程序
let m = prompt('第一个数');
let n = prompt('第二个数');
for(let i=(m>n?m:n);i<=m*n;i++){
if(i%m===0 && i%n===0){
document.write(i);
break;
}
}
//最终程序2,最小公倍数范围是较大数至m*n内,且最小公倍数除较大数,是1、2、3这种整数才行,也就是说,这个范围还可以缩小,去掉不是较大数的倍数!所以改变步长为较大数`i+较大数`
let m = Number(prompt('第一个数'));
let n = Number(prompt('第二个数'));
let larger = m>n?m:n;
for(let i=larger;i<=m*n;i=i+larger){
if(i%m===0 && i%n===0){
document.write(i);
break;
}
}

2.函数

也可以看成是一种数据类型,只不过可以存储一大段代码,用function关键字定义,分定义和调用!

  • 函数定义阶段(声明函数):把代码装进盒子,不执行函数体内代码
  • 函数调用阶段:把盒子里面的代码执行
function 标识符函数名字(){

装在盒子里的代码
...

}

定义阶段

关键字function后写函数名()内可以放变量,叫做形参{}内可以存放代码。

  • 形参是个特殊的变量,只能函数内部使用,值由调用时,实参决定!

调用阶段

函数名();:调用函数时,就能执行{}内的代码,()可以输入值,叫做实参,也就是给形参赋值!某些函数运行后可以返回值!也就是调用函数后的运行结果!

为什么需要函数

函数可以把实现一种功能,或解决一类问题的代码块存储起来,通过一个函数名就可以调用这一长串代码,用来完成某项任务或者计算,通常把实现某种具体功能的代码封装在函数{}内,比如求阶乘,求最小公倍数。一个函数解决一类问题,只需要输入值,就能输出计算后的结果!

案例:求10的阶乘函数!

//定义函数
function factorial(n){
let sum=1;
while(n>=1){
sum =sum*n;
n--;
}
document.write(sum);
}
//调用函数
factorial(10);

调用函数factorial();括号内可以给任意正整数!就可以求相应整数的阶乘,一个函数实现一项完整功能!

案例:11的阶乘输出到控制台,18的阶乘输出到页面中!

function factorial(n){
let sum=1;
while(n>=1){
sum =sum*n;
n--;
}
return sum;
}
document.write(factorial(18));//调用函数,赋值为18,计算结果为18的阶乘,并显示在页面上
console.log(factorial(11));//调用函数,赋值为11,计算结果为11的阶乘,显示在控制台上

return 值;

当函数有返回值时,可以把函数本身看成是一个值,调用函数时,相当于一个加工工厂,一个有输入,有输出的值!可以把这类函数参与到其它运算中!

这才是函数最重要的功能之一,像一个处理工厂,给原料,就能加工成成品!当然这种函数通常只加工!只处理运算,运算结果只存在于计算机内存中,要想使用输出的结果,还需要另外的代码处理!

没有返回值时,主要把函数看成一个完整功能,有返回值时,可以把函数制当成一个值(处理值的值)!

带有返回值的函数

当函数调用时,添加结果!作为函数的返回值!即运算后是一个值!又变成了一个数据!

像算术运算,+、-、*、/都可以看成是一个函数,运算符两边是输入,返回运算结果!

//看看之前用到的函数
let num = Number('666');//输入值'666',返回值666,参与其它运算,赋值给num!
let str = String(7);

//同理,上一个求阶乘函数
let fac = factorial(18);//输入值18,返回值18的阶乘结果,参与其它运算,赋值给fac!

递归函数

函数的一种应用方式,==递==:向下逐层传递,==归==:向上逐层返回

在调用函数的时候,把问题逐层解决!比如排队时,想知道自己是第几个,问前一个直到第一个,再逐层返回答案;

在排队时,想知道自己的位置,把位置当中一个函数,输入参数,这个函数等于前一个人的位置函数+1,这个过程就是递!

位置(我)=位置(前一个人)+1

在这个函数中,设置一个终点,也就是函数递到某种程度后,就有一个明确的值,比如第一个人知道自己的位置是1

如果位置(第一个人) 返回1

特点是,你的问题变成了前一个人的问题,逐层类推,你的问题变成大家的问题,大家的问题解决了,你的问题就解决了!只要前面有一个人知道答案,后面直到你都能知道答案!

使用递归

我和前一人是同一类型问题,同一函数就可以解决!且我的位置要在前一位置的结果上计算!并且函数最终阶段有返回值,让函数本身变成一个值参与运算,继续返回值,不断消解问题,得出结果!

函数本身即是一串代码集合,又是一个值!递的过程就是累计代码,一直递到函数返回具体的值时,出现归的过程,函数参与运算,不断消解函数表达式,巧妙在于,返回的结果是问题本身的运算!

当一个问题可以设计成问题本身参与运算时,便可以用递归,设计时先想结果!即返回值形式!

案例:100的阶乘

100!=100*99*98*97*96*95*94...*3*2*1

100!=100*99*98*97*96*95*94...*3*2*1
100!=100*99! //100!和99!是同一类型问题,一类函数就可以解决,此时100!是我们需要的答案!也就是函数返回值!然后自己调用自己!
99!=99*98!
...
2!=2*1

//递进关系
function factorial(n){
return n*factorial(n-1);
}

递归

一个函数调用了它自身,这种现象就称为递归!

//最终函数,求10!
function factorial(n){
if(n===1) return 1; //设置折返点!不然无穷向下递进,没有归的过程!1的阶乘不需要计算
return n*factorial(n-1);
}
document.write(factorial(10));

递归函数

一个函数调用自身,并设置了结束条件,这个函数才是一个正确的递归函数!

案例:斐波那契数列!求数列第n位是多少?

//规则:第一位和第二位必然是1,第三位开始,每位是前两个数之和!
1 1 2 3 5 8......
//第n位是函数返回值,也就是函数的计算结果!且第n位是第(n-1)位与第(n-2)位之和
fbl(n)=fbl(n-1)+fbl(n-2);
return fbl(n-1)+fbl(n-2);
//每个fbl()都可以拆成fbl(-1)+fbl(-2),子辈也可以看成是新的fbl(),无限套娃!
fbl(n)=fbl(n-1)+fbl(n-2);
=fbl(n-2)+fbl(n-3)+fbl(n-3)+fbl(n-4);
...
=fbl(n-m)+fbl(n-m-1)+fbl(n-m-2)+...+fbl(n-2m);

//其中n和m都是已知量,无非是以下形式,能消则消,不能消就继续拆!
...
fbl(5)=fbl(4)+fbl(3) //fbl(4)+fbl(3),还是无法消除
fbl(4)=fbl(3)+fbl(2) //返回fbl(3)+1,消除了一个
fbl(3)=fbl(2)+fbl(1) //返回1+1,消除了两个

//每个fbl()形式都可以转换成无数个更小的两个fbl()之和,最终会转换到fbl(2),fbl(1)!赋值fbl(2)和fbl(1)为1,就可以消去函数,变为数字1;
//就像下雨,从天上分散下来,落到地面就消失,fbl()拆分时遇到fbl(2)和fbl(1)形式就变成数字1

//最终程序!
function fbl(n){
if(n===1 || n===2) return 1; //最终是fbl(2)或fbl(1)时,返回1;
return fbl(n-1)+fbl(n-2);
}
let m=Number(prompt('请输入需要求的斐波那契数列第几位'));
document.write(fbl(m));

作用域

一个变量可以在什么范围内使用,有层级关系,像倒树状!父子,兄弟!

  • 全局作用域:一个页面中出现的变量,就是一个全局变量
  • 私有作用域:只在==函数==才能生成私有作用域!

作用域

变量使用范围

  • 定义变量时:声明在什么位置的变量,就作用于哪个范围,就是哪一个作用域的变量!
  • 访问变量时:获取变量的存储的值,这一层没有就用父级,以此类推,全局都没有就报错!
  • 给变量赋值时:变量赋值,这一层没有这个变量,就父级那一层找,以此类推,都没有就先定义一个全局变量再赋值!
let a = 100;
function fn(){
let a = 10;
a++;
return a;
}
console.log(fn());
console.log(fn());

// 返回11、11,这就是函数的局部变量特征

如果想多执行几遍,变量的值实现递增,就需要变量在执行后不释放,就需要使用闭包!

闭包

与函数中的变量一样,避免变量全局污染,使数据私有化,外部无法修改但可以让外部使用!并且重要的是使变量可以驻留在内存,不被回收!

注意需要在函数内部,再嵌套一层函数,然后让内层函数使用外层函数的变量,那么这个变量就不会被释放,存放在闭包里面,如果想继续使用内部函数,直接在内部函数前加个return就行!

let a = 100;
function fn(){
let a = 10;
return function(){
a++;
return a;
}
}
let f = fn();
console.log(f());
console.log(f());
console.log(f());

// 返回11,12,13,闭包更强大,可以函数中的变量在执行后,不被释放

image-20230525194605656

定义好闭包后,当执行这个闭包函数时,闭包变量就完成了

也就是调用外层函数,把内部函数保存在一个变量中时,闭包变量就创建好了,当执行这个内部函数,可以实现内部函数中的变量递增,说明这个变量没有被重新赋值,并且一直保存在内存中,没有在执行后释放!

只是在把内部函数赋值给其它变量时,执行了一次外部函数,其余都是执行内部函数,所以内部函数中的变量只进行了一次赋值,后续不会重新赋值!并且每次执行内部函数,在闭包中的变量不会被释放!长期驻留在内存中!

闭包双刃剑

变量在内存中不释放即是优点,有是缺点,处理不当容易造成内存泄漏,可以选择手动清理内存

f = null;

高级数据类型

对象

就是一个可以装很多数据的盒子,就像个书架,可以放很多书,通过书名就可以查看书的内容!

创建对象:let obj = {}

和创建变量类似,制作个盒子,不同的是有个大括号{},目前创建的对象是个空对象,里面可以存放键值对,就是名:值,对象相当于键值对的集合!每个键值对用逗号隔开!

对象的操作:对象内键值对的操作

  • 增:对象内添加一个键值对

    • obj.name = '小可爱'
    • obj['name'] = '小可爱'[]内是字符串格式
  • 删:使用delete关键字,删除对象内一个键值对

    • delete obj.name
    • delete obj['name']
  • 改:修改对象内一个键值对的,与增的语法一致,有则改,无则增!

  • 查:访问对象内一个键值对的

    • obj.name
    • obj['name']
  • 遍历对象

    • 语法:for (variable in object)
    • variable:在每次迭代时,variable 会被赋值为不同的属性名
    • object:非 Symbol 类型的可枚举属性被迭代的对象。
<script>
let users = {id: 1, name: '前端小灰狼', age: 28 };
for (let key in users) {
console.log(key);
console.log(users[key]);
}
</script>

数组

有序的数据的集合!

创建数组:let arr = []

和创建变量类似,制作个盒子,不同的是有个大括号[],直接把想要储存的数据写里面,不需要取名字!因为每个值都有序号,这是有顺序的集合,每个值用逗号隔开!可以存储任何数据类型

  • 索引(下标):从0开始,依次加1;

数组的操作:

  • 长度的操作

    • 获取长度:数组名.length
    • 设置长度:数组名.length=数字,赋值就行,少于原始长度时,后续值删除!
  • 数据的操作

    • 获取:数组名[索引]
    • 设置:数组名[索引]=值,赋值就行
  • 数组的==遍历==:依次访问数组中每个数据!利用循环,步长为1,从0开始!变量为索引!

排序算法

冒泡排序

把数据为乱序的数组,排列成顺序数据,冒泡是其中的一种方法!

冒泡排序是把前两个数据,比较大小,设置前>后,当为真时,交换位置,重复操作!操作数倒三角

//a=[6,4,7,3,1,9,2,5,8],从前都后,两两比较,大则交换,一轮结束,最大在最后,重复操作!
//设置两个变量,比较,交换值!
//let a=[6,4,7,3,1,9,2,5,8];
//执行操作,两个数交换位置,即需要第三个杯子!什么时候交换,需要if语句
if(a[0]>a[1]){
cup = a[0];
a[0] = a[1];
a[1] = cup;
}
//谁和谁交换,设置个变量,来选择第几位数!
//满足倒三角,需要两层循环变量!并调整其初始值和步长,成为选择数组的下标!
//最终程序,创建个中间变量,负责交换中存放值!
let a=[6,4,7,3,1,9,2,5,8];
let aleng = a.length;
for(let k=aleng;k>=1;k--){ //外层循环是,需要排(数组长度)-1个最大值!
for(let i=0,j=1;i<k;i++,j++){ //内层循环是遍历一遍,排好一个最大值
let cup;
if(a[i]>a[j]){ //if语句复制判断交换,j可以不用设置,使用i+1;
cup = a[i];
a[i] = a[j];
a[j] = cup;
}
}
}
console.log(a);
//最终版2
//最终程序,创建个中间变量,负责交换中存放值!
let a=[6,4,7,3,1,9,2,5,8];
let cup;
for(let k=a.length;k>1;k--){ //外层循环是,需要排(数组长度)-1个最大值!
for(let i=0;i<k-1;i++){ //内层循环是遍历一遍,排好一个最大值,第k-1个数与k交换
if(a[i]>a[i+1]){ //if语句复制判断交换,j可以不用设置,使用i+1;
cup = a[i];
a[i] = a[i+1];
a[i+1] = cup;
}
}
}
console.log(a);

选择排序

牢记:不要过多数据,需要关注索引

设第一个为最小值,依次比较,遇到更小的数,把索引记下来,让更小的数继续依次,谁小谁当更小的数,一直比较至末尾,最后把最小的数字与假设的值交换!

//假设最小数字的索引为:0,真实最小数字的索引为:5
//更小的数的索引,记录下来
let min;
if(a[0]>a[1]){
min=1;
}
//设置变量,利用循环依次比较,下标对应的变量,比较一轮,找出最小数的下标
let min = 0;
for(i=1;i<a.length;i++){
if(a[min]>a[i]){
min=i;
}
}
//利用下标,交换'真实最小数'与'设定的最小数'的位置!
let cup;
cup = a[0];
a[0] = a[min];
a[min] = cup;
//用嵌套执行下一轮,利用嵌套循环的变量,决定下一轮从新开始比较的位置,也就是设定新的最小值!
//最终程序
let a=[6,4,7,3,1,9,2,5,8];
let sizenum = a.length;
let cup;
for(j=0;j<sizenum-1;j++){ //让变量j当设置的最小值下标!
let min = j;
for(i=j+1;i<sizenum;i++){ //循环一轮,找出这轮最小值下标
if(a[min]>a[i]){
min=i;
}
}
cup = a[j]; //把这轮最小值与设定的最小值交换!
a[j] = a[min];
a[min] = cup;
}
console.log(a);

JS中内置方法

数组常用方法

帮助我们对数组进行快速操作,记住就行,没有逻辑

  • push()方法,将数据追加到数组末尾
    • 语法:数组.push(数据)
    • 返回值:数组最新的长度
  • pop()方法,删除数组末尾数据
    • 语法:数组.push()
    • 返回值:被删除的数据,一个数据值!
  • unshift()方法,数组最前面添加一个数据
    • 语法:数组.unshift(数据)
    • 返回值:数组最新的长度
  • shift()方法,删除数组最前面的数据
    • 语法:数组.shift()
    • 返回值:被删除的数据,一个数据值!
  • reverse()方法,将数组反转
    • 语法:数组.reverse()
    • 返回值:反转后的数组,一个数组
  • splice()方法,删除数组中若干数据,并选择是否插入新的数据!截取并拿走,会改变数组
    • 语法:数组.splice(开始索引,多少个,要插入的数据)
      • 开始索引:默认为0
      • 多少个:默认为0
      • 要插入的数据:默认没有,如果写的话,从哪儿删除,从哪儿插入
    • 返回值:以数组的形式,返回被删除的数据
  • sort()方法,对数组进行排序
    • 语法有三种:
      • 数组.sort(),结果为位排序
      • 数组.sort(function(a,b){return a-b}),传递一个函数作为参数,结果为升序
      • 数组.sort(function(a,b){return b-a}),传递一个函数作为参数,结果为降序
    • 返回值:排序好的数组

  • join()方法,将数组变成一个字符串,形式为数据+连接符,原数组无变化!

    • 语法:数组.join(连接符)
    • 返回值:连接好的字符串
  • concat()方法,将其它数组和原始数组拼接在一起!两个数组合并成一个数组。

    • 语法:数组.concat(其它数组)
    • 返回值:合并好的数组
  • slice()方法,截取数组中部分数据,相当于切片面包

    • 语法:数组.slice(开始索引,结束索引)
      • 开始索引:默认为0
      • 结束索引:默认为数组长度,注意是索引包括之前,不包括索引之后的数据。
    • 返回值:以数组形式,返回截取的数据
  • indexOf()方法,查找数据,在数组中,的索引

    • 语法:数组.indexOf(数据)
    • 返回值:一个数字
      • 有该数据,返回第一次出现的索引位置;
      • 没有该数据,返回-1
  • forEach()方法,遍历数组

    • 语法:数组.forEach(function(item,index,arr){})
    • 传递一个函数作为参数,数组有多少个数据,这个函数就执行多少次!
      • item:数据的每一项
      • index:索引
      • arr:原始数组
    • 返回值:无
    • 语法:for(定义变量 in 数组)
    • 在for/in循环结构中,变量表示数组的下标!
    • 还可以使用for(let i=0; i<a.length; i++){}遍历数组!
    let arr=[1,2,3,4,5,6,7,8]
    arr.forEach(function(item,index,arr){
    console.log(item);
    console.log(index);
    console.log(arr);
    })
  • map()方法,映射数组

    • 语法:数组.map(function(item,index,arr){})
    • 添加一个映射条件即可,以return的方式书写
      • item:数据的每一项
      • index:索引
      • arr:原始数组
    • 返回值:映射好的数组
let arr=[1,2,3,4,5,6,7,8]
let retnum=arr.map(function(item,index,arr){
return item*10; //以return的方式书写映射条件
})
console.log(retnum);
  • filter()方法,过滤数组

    • 语法:数组.filter(function(item,index,arr){})
    • 添加一个过滤条件即可,以return的方式书写
      • item:数据的每一项
      • index:索引
      • arr:原始数组
    • 返回值:过滤好的数组
    let arr=[1,2,3,4,5,6,7,8]
    let retnum=arr.filter(function(item,index,arr){
    return item>5; //以return的方式书写过滤`条件`
    })
    console.log(retnum);
  • every()方法,判断数组中每个数据是否满足条件,都满足则返回ture

    • 语法:数组.every(function(item,index,arr){})
    • 添加一个条件即可,以return的方式书写
      • item:数据的每一项
      • index:索引
      • arr:原始数组
    • 返回值:一个布尔值,代表是否每一项都满足条件
    let arr=[1,2,3,4,5,6,7,8]
    let retnum=arr.every(function(item,index,arr){
    return item>5; //以return的方式书写判断`条件`
    })
    console.log(retnum); //返回false
    let arr=[1,2,3,4,5,6,7,8]
    let retnum=arr.every(function(item,index,arr){
    return item>0; //以return的方式书写判断`条件`
    })
    console.log(retnum); //返回ture
  • some()方法,判断数组中,是否有数据满足条件,有一个满足则返回ture

    • 语法:数组.some(function(item,index,arr){})
    • 添加一个条件即可,以return的方式书写
      • item:数据的每一项
      • index:索引
      • arr:原始数组
    • 返回值:一个布尔值,代表至少有一项满足条件
    let arr=[1,2,3,4,5,6,7,8]
    let retnum=arr.some(function(item,index,arr){
    return item>5; //以return的方式书写判断`条件`
    })
    console.log(retnum); //返回ture
  • 总结,前7个方法都会直接改变数组,后9个不改变原始数组!

字符串常用方法

字符串可以看成是数组,也是按照索性进行排列的,一个字符就是一个索引位置!这就是字符串的排列!

  • charAt()方法,获取字符串索引位置的字符,从索引是从0开始计数。
    • 语法:字符串.charAt(索引)
    • 返回值:对应索引的字符
  • toLowerCase()方法,将字符串中字母全部转换成小写
    • 语法:字符串.toLowerCase()
    • 返回值:转换好的字符串
  • toUpperCase()方法,将字符串中字母全部转换成大写
    • 语法:字符串.toUpperCase()
    • 返回值:转换好的字符串
  • replace()方法,替换字符串,将字符串内,第一个 满足换下内容的片段替换成换上内容
    • 语法:字符串.replace(换下内容,换上内容)
    • 返回值:替换好的字符串
  • trim()方法,去掉字符串首尾空格!中间空格不会去掉!
    • 语法:字符串.trim()
    • 返回值:去掉首位空格的字符串
  • split()方法,按照分隔符,将字符串,切割成一个数组!这个分隔符存在于字符串中!
    • 语法:字符串.split(分隔符)
    • 返回值:切割数据后的字符串数组!
  • 统一说三个方法,都是截取字符串,返回截取出来的字符串
    • substr()
      • 语法:字符串.substr(开始索引,==多少个==);
    • substring()
      • 语法:字符串.substring(开始索引,结束索引);
    • slice()
      • 语法:字符串.slice(开始索引,结束索引);
  • 总结,都不会改变原来的字符串,以返回值方式使用!

数字常用方法

  • random(),获取0~1之间的随机小数,包含0,但不包含1,小数点后最多保留17位!

    • 语法:Math.random()
    • 返回值:0~1之间的随机小数!
  • round(),对数字进行四舍五入的取整

    • 语法:Math.random()
    • 返回值:四舍五入后的整数
    console.log(Math.random(10.4));
    //打印10
  • 两个方法取整

    • Math.ceil(),向取整
      • 语法:Math.ceil(数字)
      • 返回值:向上取整后的整数
    • Math.floor(),向取整
      • 语法:Math.floor(数字)
      • 返回值:向下取整后的整数
  • pow(),幂运算

    • 语法:Math.pow(底数,指数)
    • 返回值:运算结果
  • sqrt(),算术平方根

    • 语法:Math.sqrt(数)
    • 返回值:运算结果
  • abs(),绝对值

    • 语法:Math.abs(数)
    • 返回值:运算结果
  • max(),最大值

    • 语法:Math.max(数1,数2,数3···)
    • 返回值:运算结果
  • min(),最小值

    • 语法:Math.min(数1,数2,数3···)
    • 返回值:运算结果
  • PI,Π值,这是个属性

    • 语法:Math.PI
    • 返回值:运算结果

案例1:获取一个范围内的随机整数

  • 获取0到10之间的随机整数
//获取随机数
Math.random();
//更改至需要范围
let num = Math.random()*10;
//更改为整数
Math.round(num);

//但是,数字0和数字10取到的概率更小
//更改范围,更改取整方法
let num = Math.random()*11;
Math.floor(num);
  • 获取0到20之间的随机整数
let num = Math.random()*21;
Math.floor(num);
  • 总结,获取0~X之间的随机数,Math.random()*(X+1),再向下取整!
  • 获取X~Y之间的随机整数
//借助0~X之间的随机数的方法思想
Math.random()*(X+1);
//从X起步难,那就截去X
//从X~Y差值的整数,比如23~28的差值是5,那就取0~5之间的整数
Math.random()*(Y-X+1);
//最后加上X
let num = X+Math.random()*(Y-X+1);
Math.floor(num);
//完整程序
let X = Number(prompt('第一个数'));
let Y = Number(prompt('第二个数'));
let num = X+Math.random()*(Y-X+1);
document.write(num);
document.write('<br/>');
document.write(Math.floor(num));
  • 封装函数
function randomNum(min,max){
return Math.floor(min+Math.random()*(max-min+1));
}

时间常用方法

时间(Date)也是JS中的一个数据类型,通过时间对象创建时间实例!

创建时间变量,new Date()创建当前终端时间,new Date(代表年的数字,月,日,时,分,秒),注意月

let time = new Date()

获取时间

  • 时间对象.getFullYear(),获取年份
  • 时间对象.getMonth(),获取月份,返回数值!0代表一月,以此类推!
  • 时间对象.getDate(),获取日期
  • 时间对象.getHours(),获取时
  • 时间对象.getMinutes(),获取分
  • 时间对象.getSeconds(),获取秒
  • 时间对象.getDay(),获取星期几,0代表周日!
  • 时间对象.getTime(),获取时间戳,单位为ms毫秒

设置时间

把上述get变成set,没有设置星期,是按日期自动匹配,带有与日期相关的数值参数!

  • 时间对象.setFullYear(数字),获取年份
  • 时间对象.setMonth(数字),获取月份,返回数值!0代表一月,以此类推!
  • 时间对象.setDate(数字),获取日期
  • 时间对象.setHours(数字),获取时
  • 时间对象.setMinutes(数字),获取分
  • 时间对象.setSeconds(数字),获取秒
  • 时间对象.setTime(数字),把数字的时间戳单位为ms毫秒,转化为年月日时分秒,传递给时间对象

案例:封装函数,获取两个时间节点之间的时间差!

在时间轴上,有个``格林威治时间`起点:1970-1-1 0:0:0

//两个时间戳相减,再换算成时间即可
//创建时间变量
let timeBegin = new Date(1996,2,19,6,10,53);
let timeEnd = new Date(2023,4,19,18,9,26);
//转换为时间戳
let chMS1 = timeBegin.getTime();
let chMS2 = timeEnd.getTime();
let num = chMS2-chMS1;
//转换为时间,毫秒换算成秒
let s = Math.ceil(num/1000);
//秒再换算成天
let day = Math.floor(s/(60*60*24));
//在天的基础上,天除不尽的,再想办法换算成小时、以此类推,再换算成分钟、秒
//以秒计算的,余数的单位也是秒,在秒的基础上换算成小时
let hours = Math.floor((s%60*60*24)/(60*60));
//以秒计算的,余数的单位也是秒,余数的单位是秒,在秒的基础上换算成分钟
let minutes = Math.floor((s%60*60)/60);
//以秒计算的,余数的单位也是秒,余数的单位是秒,不用换算
let seconds = s%60;
console.log(day+'天'+hours+'时'+minutes+'分'+seconds+'秒');

let timeBegin = new Date(1996,2,19,6,10,53);
let timeNow = new Date(2023,4,19,18,9,26);
let birth = new Date();
birth.setTime(timeNow.getTime()-timeBegin.getTime());
year = birth.getFullYear() - 1970;
month = birth.getMonth() - 1;
date = birth.getDate() - 1;
hour = birth.getHours();
minutes = birth.getMinutes();
seconds = birth.getSeconds();
console.log('现在是'+year+'岁'+month+'月'+date+'天'+hour+'时'+minutes+'分'+seconds+'秒');
//最终程序
function spaceTime(time1,time2){
let s = Math.ceil((time2.getTime()-time1.getTime())/1000);
let day = Math.floor(s/(60*60*24));
let hours = Math.floor((s%60*60*24)/(60*60));
let minutes = Math.floor((s%60*60)/60);
let seconds = s%60;
ruture {day:day,hours:hours,minutes:minutes,seconds:seconds}
}

let timeBegin = new Date(1996,2,19,6,10,53);
let timeEnd = new Date(2023,4,19,18,9,26);
console.log(spaceTime(timeBegin,timeEnd));