js基础入门
js基础入门
dleei浏览器执行JS简介
浏览器分成两大部分 渲染引擎和JS 引擎
渲染引擎: 用来解析HTML与CSS,俗称内核,比如chrome浏览器的blink,老版本的webkit JS 引擎:也称为JS 解释器。用来读取网页中的JavaScript代码,对其处理后运行,比如chrome浏览器的v8
浏览器本身并不会执行JS代码,而是通过内置JavaScript引擎(解释器)来执行JS代码。JS引擎执行代码时逐行解释每一句源码(转换为机器语言),然后由计算机去执行,所以Javascript语言归为脚本语言,会逐行解释执行
概念
是一种运行在客户端(浏览器)的编程语言,可以用来创建动态更新的内容,控制多媒体,制作图像动画等交互效果
组成
ECMAScript: ECMAScript 规定了JS的编程语法和基础核心知识,是所有浏览器厂商工共同遵守的一套JS语法工业标准
DOM—文档对象模型(Document Object Model)
文档对象模型,是W3C组织推荐的处理可扩展标记语言的标准编程接口。通过DOM提供的接口可以对页面上的各种元素进行操作(大小,位置,颜色等)
BOM—浏览器对象模型(Browser Object Model)
BOM 是指浏览器对象模型,他提供了独立于内容的,可以与浏览器窗口进行互动的对象结构。通过BOM可以操作浏览器窗口,比如弹出框,控制浏览器跳转,获取分辨率等
上面的对象模型会在下一篇的WebAPI进行详细的介绍
书写位置
内部js
外部js
1 | <body> |
script
标签中间无需写代码,否则会被忽略!
行内js
1 | <button onclick="alert('你好,js')">点击我月薪过万</button> |
js注释
单行注释
1 | //这是单行注释 |
多行注释
1 | /*这是多行注释,假装有很多的内容 |
输入 、输出语句
输出语句
弹出框语句
语法: alert
1 | alert('页面弹出内容') |
控制台输出语句
语法:console.log
1 | console.log('hello word!') |
页面输出(给用户展示文本,可解析标签)
语法:document.write
1 | document.write('页面输出内容') |
输入语句
输入框语句
prompt
函数接收两个参数:
1 | result = prompt('title', [default]); |
浏览器会显示一个带有文本消息的模态窗口,还有 input 框和确定/取消按钮
1 | title |
显示给用户的文本
1 | default |
可选的第二个参数,指定 input 框的初始值(未输入值或值为空,初始值就会生效)
举个栗子:
1 | let age = prompt('How old are you?', 100); |
IE 浏览器会提供默认值
第二个参数是可选的。但是如果我们不提供的话,Internet Explorer 会把 "undefined"
插入到 prompt。
我们可以在 Internet Explorer 中运行下面这行代码来看看效果:
1 | let test = prompt("Test"); |
所以,为了 prompt 在 IE 中有好的效果,建议始终提供第二个参数:
1 | let test = prompt("Test", ''); // <-- for IE |
嫌麻烦的同学,可以直接忽略第二个参数,写成这样
1 | prompt('页面输入内容') |
语法:confirm
1 | confirm('页面弹出的内容') |
confirm
函数显示一个带有用户输入的内容以及确定和取消两个按钮的模态窗口。
点击确定返回 true
,点击取消返回 false
例如:
1 | let isBoss = confirm("Are you the boss?"); |
这个语法和 prompt 语法不能说一模一样,只能说是非常的神似,不过使用体验上看上去有点鸡肋
上述所有方法共有两个限制:
1.模态窗口的确切位置由浏览器决定。通常在页面中心
2.窗口的确切外观也取决于浏览器。我们不能修改它
结束符
分号;
结束符可写可不写;(一行显示必须要加)
实际看团队要求,保证风格统一
数据类型
基本数据类型
检测数据类型
typeof
运算符返回参数的类型。当我们想要分别处理不同类型值的时候,或者想快速进行数据类型检验时,非常有用
对 typeof x
的调用会以字符串的形式返回数据类型:
1 | typeof undefined // "undefined" |
你可能还会遇到另一种语法:
typeof(x)
。它与typeof x
相同。简单点说:
typeof
是一个操作符,不是一个函数。这里的括号不是typeof
的一部分。它是数学运算分组的括号。通常,这样的括号里包含的是一个数学表达式,例如
(2 + 2)
,但这里它只包含一个参数(x)
。从语法上讲,它们允许在typeof
运算符和其参数之间不打空格,有些人喜欢这样的风格。有些人更喜欢用
typeof(x)
,尽管typeof x
语法更为常见。
数字型
number
(整数,小数,分数,负数)
作用:做数学运算
检测数据类型返回结果number
除了常规的数字,还包括所谓的“特殊数值”也属于这种类型:Infinity
、-Infinity
和 NaN
。
Infinity
代表数学概念中的 无穷大 ∞。是一个比任何数字都大的特殊值。
我们可以通过除以 0 来得到它:
1 | alert( 1 / 0 ); // Infinity |
或者在代码中直接使用它:
1 | alert( Infinity ); // Infinity |
NaN
代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果,比如:
1 | alert( "not a number" / 2 ); // NaN,这样的除法是错误的 |
NaN
是粘性的。任何对 NaN
的进一步数学运算都会返回 NaN
:
1 | alert( NaN + 1 ); // NaN |
所以,如果在数学表达式中有一个 NaN
,会被传播到最终结果(只有一个例外:NaN ** 0
结果为 1
,在数学运算中任何数的0次方都等于1)。
字符串型
string
在 JavaScript 中,有三种包含字符串的方式。
- 双引号:
"Hello"
. - 单引号:
'Hello'
. - 反引号:
Hello
.
双引号和单引号都是“简单”引用,在 JavaScript 中两者几乎没有什么差别
反引号是 功能扩展 引号(模版字符串)。它们允许我们通过将变量和表达式包装在 ${…}
中,来将它们嵌入到字符串中。例如:
1 | let name = "李明"; |
${…}
内的表达式会被计算,计算结果会成为字符串的一部分。可以在 ${…}
内放置任何东西:诸如名为 name
的变量,或者诸如 1 + 1
的算数表达式,或者其他一些更复杂的
这仅仅在反引号内有效,其他引号不允许这种嵌入。会被识别为字符串,直接输出
1 | alert( "the result is ${1 + 2}" ); // the result is ${1 + 2}(使用双引号则不会计算 ${…} 中的内容) |
作用:用来显示文字
检测数据类型返回结果string
boolean (布尔型)
真true
假false
这种类型通常用于存储表示 yes 或 no 的值:true
意味着 “yes,正确”,false
意味着 “no,不正确”
作用:依据条件判断真假或是正确与不正确
检测数据类型返回结果boolean
未定义型
undefind
声明了变量但未赋值
检测数据类型返回结果undefind
空类型
null
已经赋值了,但是内容为空(毛坯房)
JavaScript 中的 null
仅仅是一个代表“无”、“空”或“值未知”的特殊值。
应用场景:如果一个变量里面确定存放的是对象,但是还未准备好对象,可以先放个null
注意点:特殊的值,表示有值但是值为空
null
的类型是object
,这是由于历史原因造成的。1995年的 JavaScript 语言第一版,只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),没考虑null
,只把它当作object
的一种特殊值。后来null
独立出来,作为一种单独的数据类型,为了兼容以前的代码,typeof null
返回object
就没法改变了。
引用数据类型
数组
对象
函数
变量
作用:在内存中开辟空间存储数据
本质:变量是程序中申请的一块用来存放数据的空间。
类似我们酒店的房间,酒店就是内存,酒店内的一个房间就可以看作是一个变量
一个现实生活的类比
如果将变量想象成一个“数据”的盒子,盒子上有一个唯一的标注盒子名字的贴纸。这样我们能更轻松地掌握“变量”的概念。
例如,变量 message
可以被想象成一个标有 "message"
的盒子,盒子里面的值为 "Hello!"
我们可以往盒子里放入任何值,
并且,这个盒子的值,我们想改变多少次,就可以改变多少次
变量的使用
1.创建变量
1 | let userName |
2.通过赋值运算符,给变量赋值
1 | ueserName = 'Jack chen' |
3.通过输出语法结合变量名进行查看
1 | console.log('userName')//Jack chen |
变量声明和赋值
声明
语法:let
+变量名
1 |
|
赋值
1 | // 声明了一个年龄的变量 |
但是我们一般声明的同时直接赋值 (变量的初始化)
1 | // 变量初始化 |
变量重复声明
会报错!
1 | // 声明了一个age变量,里面存放了一个18的数据 |
在一行中同时声明多个变量
多个变量之间用逗号,
隔开
1 |
|
看上去代码长度更短,但并不推荐这样,为了更好的可读性,请一行只声明一个变量
多行变量声明有点长,但更容易阅读:建议使用
1 | let user = 'John'; |
一些程序员采用下面的形式书写多个变量:
1 | let user = 'John', |
……甚至使用“逗号在前”的形式:
1 | let user = 'John' |
技术上讲,这些变体都有一样的效果。所以,这是个个人品味和审美方面的问题。
我们还可以声明两个变量,然后将其中一个变量的数据赋值给另一个变量
1 | let hello = 'Hello world!'; |
变量更新
步骤:
1 | // 声明了一个message变量,里面存放了一个hello的数据 |
当变量的值发生了改变,之前的数据就被从变量中删除了
保留关键字
有一张 保留字列表,这张表中的保留字无法用作变量命名,因为它们被用于编程语言本身了。
比如,let
、class
、return
、function
都被保留了。
var与let区别!
1.var
可以先使用后声明,而let不用
2.var
可以重复声明而let不行
3.var
没有块级作用域,而let有,var会进行变量提升,而let不行
变量交换
交换操作(相当于就是重新赋值的过程)
需要一个新的临时变量名,来保存我们之前的变量
再声明一个新的变量
步骤:
1.声明一个新的临时空变量temp
2.把apple的 苹果汁 倒入 temp (赋值)
3.把orange橙子汁倒入 apple 杯子里面
4.把 temp里面的苹果汁倒入 orange杯子
1 | // 有一个装橙汁的容器,一个装苹果汁的容器 |
变量的命名规范
规则:
不能用关键字
关键字:有特殊含义的字符,JavaScript 内置的一些英语词汇。即上面提到的保留关键字
只能用下划线、字母、数字、$组成,且数字不能开头
字母严格区分大小写,如 Age 和 age 是不同的变量
规范:
起名要有意义,见名知意(语义化)
遵守小驼峰命名法
第一个单词首字母小写,后面每个单词首字母大写。例:userName
正确命名变量
一个变量名应该有一个清晰、明显的含义,对其存储的数据进行描述。
变量命名是编程过程中最重要且最复杂的技能之一。快速地浏览变量的命名就知道代码是一个初学者还是有经验的开发者写的。
在一个实际项目中,大多数的时间都被用来修改和扩展现有的代码库,而不是从头开始写一些完全独立的代码。当一段时间后,我们做完其他事情,重新回到我们的代码,找到命名良好的信息要容易得多。换句话说,变量要有个好名字。
声明变量之前,多花点时间思考它的更好的命名。你会受益良多。
一些可以遵循的规则:
- 使用易读的命名,比如
userName
或者shoppingCart
。 - 离诸如
a
、b
、c
这种缩写和短名称远一点,除非你真的知道你在干什么。 - 变量名在能够准确描述变量的同时要足够简洁。不好的例子就是
data
和value
,这样的名称等于什么都没说。如果能够非常明显地从上下文知道数据和值所表达的含义,这样使用它们也是可以的。 - 脑海中的术语要和团队保持一致。如果网站的访客称为“用户”,则我们采用相关的变量命名,比如
currentUser
或者newUser
,而不要使用currentVisitor
或者一个newManInTown
。
常量
也是一个容器,用来保存数据,常量的值无法修改的
1 | const age = 1 |
常量值无法修改,强制修改会报错
常量必须要初始化(定义时必须要进行赋值)
常量和变量的使用场景:
1.当存储无需修改的数据时使用常量
2.当存储需要后期修改的数据时,应当使用
let
关键字来定义变量
大写形式的常数
一个普遍的做法是将常量用作别名,以便记住那些在执行之前就已知的难以记住的值。
使用大写字母和下划线来命名这些常量。
例如,让我们以所谓的“web”(十六进制)格式为颜色声明常量
1 | const COLOR_RED = "#F00"; |
好处:
COLOR_ORANGE
比"#FF7F00"
更容易记忆。- 比起
COLOR_ORANGE
而言,"#FF7F00"
更容易输错。 - 阅读代码时,
COLOR_ORANGE
比#FF7F00
更易懂。
什么时候该为常量使用大写命名,什么时候进行常规命名?让我们弄清楚一点。
作为一个“常数”,意味着值永远不变。但是有些常量在执行之前就已知了(比如红色的十六进制值),还有些在执行期间被“计算”出来,但初始赋值之后就不会改变。
例如:
1 | const pageLoadTime = /* 网页加载所需的时间 */; |
pageLoadTime
的值在页面加载之前是未知的,所以采用常规命名。但是它仍然是个常量,因为赋值之后不会改变。
换句话说,大写命名的常量仅用作“硬编码(hard-coded)”值的别名。
字面量
什么是字面量?
只要是能被js
识别的数据都被称之为字面量
字面量不是变量和常量,字面量指的是数据不是容器,所以字面量只能在赋值运算符=右边当数据使用
1 | // 数字字面量 |
运算符
算术运算符:
加法: +
减法: -
乘法:*
除法:/
取余: %
求幂:**
算数运算符作用:进行数学运算
前四个都很简单,而 %
和 **
则需要说一说。
取余运算符是 %
,尽管它看起来很像百分数,但实际并无关联
a % b
的结果是 a
整除 b
的余数
1 | alert( 5 % 2 ); // 1,5 除以 2 的余数 |
除法取余运算符 %
应用场景
判断某个数字能否被整除
进行取余数运算时,前面一个数小于后面一个数,返回的余数则是第一个数
1 | // 控制台打印结果为5 |
求幂
求幂运算 a ** b
将 a
提升至 a
的 b
次幂。
在数学运算中我们将其表示为
例如:
1 | alert( 2 ** 2 ); // 2² = 4 |
就像在数学运算中一样,幂运算也适用于非整数。
例如,平方根是指数为 ½ 的幂运算:
1 | alert( 4 ** (1/2) ); // 2(1/2 次方与平方根相同) |
除了常规的数字,还包括所谓的“特殊数值(“special numeric values”)”也属于这种类型:Infinity
、-Infinity
和 NaN
。
Infinity
代表数学概念中的 无穷大 ∞。是一个比任何数字都大的特殊值。我们可以通过除以 0 来得到它:
1
alert( 1 / 0 ); // Infinity
或者在代码中直接使用它:
1
alert( Infinity ); // Infinity
NaN
代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果,比如:1
alert( "not a number" / 2 ); // NaN,这样的除法是错误的
NaN
是粘性的。任何对NaN
的进一步数学运算都会返回NaN
:1
2
3alert( NaN + 1 ); // NaN
alert( 3 * NaN ); // NaN
alert( "not a number" / 2 - 1 ); // NaN所以,如果在数学表达式中有一个
NaN
,会被传播到最终结果(只有一个例外:NaN ** 0
结果为1
)。
算术运算符优先级
先乘除取余,后加减,有小括号先算小括号里面的
1 | // 控制台打印结果为NaN |
计算错误会出现
NaN
,不属于数字,但属于数字类型
拼接字符串
` ` 反引号包含数据
${ 变量名 }
+
数字相加,字符相连
1 | // 页面弹出输入数据 |
${…}
内的表达式会被计算,计算结果会成为字符串的一部分。可以在 ${…}
内放置任何东西:诸如名为 name
的变量,或者诸如 1 + 2
的算数表达式,或者其他一些更复杂的。
需要注意的是,这仅仅在反引号内有效,其他引号不允许这种嵌入。
1 | alert( "the result is ${1 + 2}" ); // the result is ${1 + 2}(使用双引号则不会计算 ${ |
赋值运算符
+=
-=
\*=
/=
%=
作用:是为了快速地进行赋值操作
1 | let num = 10 |
注意点:赋值运算符,一定是把右边的值赋值给左边(变量或是常量)
链式赋值(Chaining assignments)
一个有趣的特性是链式赋值:
1 | let a, b, c; |
链式赋值从右到左进行计算。首先,对最右边的表达式 2 + 2
求值,然后将其赋给左边的变量:c
、b
和 a
。最后,所有的变量共享一个值。
同样,出于可读性,最好将这种代码分成几行:
1 | c = 2 + 2; |
这样可读性更强,尤其是在快速浏览代码的时候。
自增 / 自减运算符
作用:把变量每次加一或是减一
++自增
++在前,前缀式:++变量,先输出当前的值,+1在运算
--自减
1 | let num = 10 |
自增/自减只能应用于变量。试一下,将其应用于数值(比如 5++
)则会报错
比较运算符
< | 大于 |
---|---|
> | 小于 |
<= | 小于等于 |
>= | 大于等于 |
== | 比较数值 |
=== | 比较值和类型 |
!== | 左右两边值和类型是否不全等(有一个不满足条件) |
!= | 左右两边是否不相等 |
作用:比较两个数据之间的关系
结果:返回布尔值true / false
===
比较的是值和类型
==
比较的是值
=
是赋值
运算符优先级
逻辑运算符
逻辑与 &&
逻辑或 ||
逻辑非 !
作用:在js
里需要同时判断多个条件使用
返回值:布尔类型
逻辑与 &&
查找规则:一假则假
逻辑与 ||
查找规则:一真则真
逻辑非 !
查找规则:取反(真变假,假变真)
案例
判断一个数是否能同时被4和100整除
1 | <script> |
空值合并运算符 ‘??’
空值合并运算符(nullish coalescing operator)的写法为两个问号 ??
。
由于它对待 null
和 undefined
的方式类似,所以在本文中我们将使用一个特殊的术语对其进行表示。为简洁起见,当一个值既不是 null
也不是 undefined
时,我们将其称为“已定义的(defined)”。
a ?? b
的结果是:
- 如果
a
是已定义的,则结果为a
, - 如果
a
不是已定义的,则结果为b
。
换句话说,如果第一个参数不是 null/undefined
,则 ??
返回第一个参数。否则,返回第二个参数。
空值合并运算符并不是什么全新的东西。它只是一种获得两者中的第一个“已定义的”值的不错的语法。
我们可以使用我们已知的运算符重写 result = a ?? b
,像这样:
1 | result = (a !== null && a !== undefined) ? a : b; |
??
的常见使用场景是提供默认值。
例如,在这里,如果 user
的值不为 null/undefined
则显示 user
,否则显示 匿名
:
1 | let user; alert(user ?? "匿名"); // 匿名(user 未定义) |
在下面这个例子中,我们将一个名字赋值给了 user
:
1 | let user = "John"; alert(user ?? "匿名"); // John(user 已定义) |
我们还可以使用 ??
序列从一系列的值中选择出第一个非 null/undefined
的值。
假设我们在变量 firstName
、lastName
或 nickName
中存储着一个用户的数据。如果用户决定不填写相应的值,则所有这些变量的值都可能是未定义的。
我们想使用这些变量之一显示用户名,如果这些变量的值都是 null/undefined
,则显示 “匿名”。
让我们使用 ??
运算符来实现这一需求:
1 | let firstName = null; |
与||
比较
或运算符 ||
可以以与 ??
运算符相同的方式使用
例如,在上面的代码中,我们可以用 ||
替换掉 ??
,也可以获得相同的结果:
1 | let firstName = null; |
纵观 JavaScript 发展史,或 ||
运算符先于 ??
出现。它自 JavaScript 诞生就存在了,因此开发者长期将其用于这种目的。
另一方面,空值合并运算符 ??
是最近才被添加到 JavaScript 中的,它的出现是因为人们对 ||
不太满意。
它们之间重要的区别是:
||
返回第一个 真 值??
返回第一个 已定义的 值
换句话说,||
无法区分 false
、0
、空字符串 ""
和 null/undefined
。它们都一样 —— 假值(falsy values)。如果其中任何一个是 ||
的第一个参数,那么我们将得到第二个参数作为结果。
不过在实际中,我们可能只想在变量的值为 null/undefined
时使用默认值。也就是说,当该值确实未知或未被设置时。
隐式转换
+
号两边有一个是字符串,就会把另外一个也转成字符串
+
-
*
/
等算术运算符都会把数据转成数字类型
+
号作为正号解析可以转换为数字类型
任何数据和字符串拼接都是字符串
显示转换
数字型
Number
(数字)
转成数字类型
若字符串内有非数字,会转换失败NaN
Parseint
(数字)
只保留整数,不会四舍五入
ParseFloat
浮点数(数字)
可以保留小数
数字类型转换
语法number
(其他数据类型)
- number只能转换纯数字的字符串,非纯数字字符串转换结果是NaN
- 布尔类型转数字,true为1,false为0
- null转数字类型是0
- undefind转数字类型是NaN
其他类型转数字类型为整数
语法:parselnt
(数据)
数据要以数字开头保留整数,非数字开头结果是NaN
其他类型转数字类型为小数
语法:parseFloat 浮点数(数据)
数据要以数字开头保留小数,非数字开头结果是NaN
字符串类型转换
其他类型转字符串
方式一😀
语法:String(数据)
可以转换null和undefined
方式二
语法:变量.toString(进制)
undefind和null这两个特殊值没有toString,不能够使用,会报错
String()实际开发使用最多
布尔类型转换
语法:Boolean
其他类型转布尔类型除了(
0
,false
,null
,undefind
,NaN
,''
)转布尔为false
,其他任何类型全部都是
true
隐式转换
通过运算符实现系统内部自动转换
实际开发中尽量使用显示转换,隐式转换慎用
null与undefined的相同点与不同点
相同点:值相同,转布尔类型都是false
不同点:类型不同,转数字类型不同
语句与表达式
表达式:可以被求值的代码,由运算符组成的式子,最终会计算出一个结果
语句
语句 :一段可以执行功能的代码,是一个行为
顺序语句
单分支语句
依据条件,有选择性的执行代码
当上面的if语句控制一行代码时, { } 可省略,写在一行
但是一般情况下,不推荐
1 | let score = +prompt('请您输入您的考试分数') |
if单分支语句
1 | if(条件会默认转换为布尔类型) { |
1 | if (2 === '2') { |
双分支语句
1 | if(条件){ |
计算平、闰年案例
1 | // 输入年份 |
多分支语句
1 | if (条件一) { |
成绩判断案例
1 | <script> |
基础运算符,数学运算
术语:’一元运算符’,’二元运算符’,运算元
运算元 —— 运算符应用的对象。比如说乘法运算 5 * 2
,有两个运算元:左运算元 5
和右运算元 2
。有时候人们也称其为“参数”而不是“运算元”。
如果一个运算符对应的只有一个运算元,那么它是 一元运算符。比如说一元负号运算符(unary negation)-
,它的作用是对数字进行正负转换
1 | let x = 1; |
如果一个运算符拥有两个运算元,那么它是 二元运算符。减号还存在二元运算符形式:
1 | let x = 1, y = 3; |
严格地说,在上面的示例中,我们使用一个相同的符号表征了两个不同的运算符:负号运算符,即反转符号的一元运算符,减法运算符,是从另一个数减去一个数的二元运算符。
二元运算符 + 连接字符串
通常,加号 +
用于求和。
但是如果加号 +
被应用于字符串,它将合并(连接)各个字符串:
1 | let s = "my" + "string"; |
注意:只要任意一个运算元是字符串,那么另一个运算元也将被转化为字符串
举个例子:
1 | alert( '1' + 2 ); // "12" |
你看,第一个运算元和第二个运算元,哪个是字符串并不重要。
下面是一个更复杂的例子:
1 | alert(2 + 2 + '1' ); // "41",不是 "221" |
在这里,运算符是按顺序工作。第一个 +
将两个数字相加,所以返回 4
,然后下一个 +
将字符串 1
加入其中,所以就是 4 + '1' = '41'
。
1 | alert('1' + 2 + 2); // "122",不是 "14" |
这里,第一个操作数是一个字符串,所以编译器将其他两个操作数也视为了字符串。2
被与 '1'
连接到了一起,也就是像 '1' + 2 = "12"
然后 "12" + 2 = "122"
这样
是不是看这些文字理解起来有些困难,用生活中的一个例子:运算符按照顺序工作,就像仓库里堆积着一些油漆,其中有一桶漏了,而它只会影响到下面的物品,将其染上了色,并不会影响到上面的物品
二元 +
是唯一一个以这种方式支持字符串的运算符。其他算术运算符只对数字起作用,并且总是将其运算元转换为数字。
下面是减法和除法运算的示例:
1 | alert( 6 - '2' ); // 4,将 '2' 转换为数字 |
数字转化 、一元运算符 +
加号 +
有两种形式。一种是上面我们刚刚讨论的二元运算符进行算术的加法运算,还有一种是一元运算符。
一元运算符加号,或者说,加号 +
应用于单个值,对数字没有任何作用。但是如果运算元不是数字,加号 +
则会将其转化为数字。
例如:
1 | // 对数字无效 |
它的效果和 Number(...)
相同,但是更加简短。
我们经常会有将字符串转化为数字的需求。比如,如果我们正在从 HTML 表单中取值,通常得到的都是字符串。如果我们想对它们求和,该怎么办?
二元运算符加号会把它们合并成字符串:
1 | let apples = "2"; |
如果我们想把它们当做数字对待,我们需要转化它们,然后再求和:
1 | let apples = "2"; |
总结:
1.数字相加
2.字符相连
3.非数字类型转化为数字类型进行求和操作
三元运算符(三元表达式)
语法: 条件? 满足条件的表达式 : 不满足条件的表达式
1 | 3<5 ? alert('真的') : alert('假的') |
switch语句
1 | switch (表达式) { |
简易计算器案例
1 | <script> |
水果查询案例
1 | let frults = prompt('请输入一种水果') |
等值(值和类型)比较直接显示结果
break关键字表示case这一项的介绍,如果每一项不加break则会造成穿透(代码逐层往下执行)
实际开发中短的额分支推荐使用if多分支语句,若是长的分支推荐使用switch语句,性能方面switch语句性能比if多分支要高
循环语句(无限循环)
while无限循环
while循环
1 | while (condition) { |
当 condition
为真时,执行循环体的 code
。
例如,以下将循环输出当 i < 3
时的 i
值:
1 | let i = 0; |
1 | while (true) { |
for无限循环
1 | for (; ;) { |
1 | // 初始值 |
for循环😀
for循环三要素
1.定义变量初始值
2.定义变量循环条件
3.设置变量变化量
1 | for (变量初始值;变量循环条件;变量变化量) { |
1 | for (let i = 1; i <= 5; i++) { |
求0-100偶数和案例
1 | let sum = 0 |
中止循环
break 中止整个循环,一般用于结果已经得到, 后续的循环不需要的时候可以使用(提高效率)
continue中止本次循环,一般用于排除或者跳过某一个选项的时候
循环嵌套
外层循环一次,里层循环多次
运行路径:外层循环一次,内层全部循环一次(外层循环一次,内层全部执行完毕之后,才会执行外部的第二次循环)
1 | // 外层for循环控制行数 |
数组
概念
数组是一个引用数据类型
作用:在单个变量中存储多个数据
使用步骤
定义数组
let 数组名 = [ 数据1,数据2,数据3] ,多个数据以逗号隔开
1 | let arr = ['张三','李四','王五','卧龙','凤雏',111] |
空数组 [ ]
1 | let arr = [] |
数组取值
数组名[索引或下标]
它其实与 obj[key]
相同,其中 arr
是对象,而数字用作键(key),它们扩展了对象,提供了特殊的方法来处理有序的数据集合以及 length
属性。但从本质上讲,它仍然是一个对象
1 | class[1] |
数组或下标开始由0开始
循环数组(遍历数组)
遍历数组:把数组中每个数据都访问到
数组长度: 数组名.length
因为数组的索引是从0开始的,所以循环初始值定义
数组求和
数组的增删改查
新增 | |
---|---|
push | 在尾部新增 |
unshift | 在头部新增 |
删除 | |
pop | 删除最后一个元素 |
shift | 删除第一个元素 |
push/shift方法
push
在末端添加一个元素
shift
取出队列首端的一个元素,整个队列往前移,这样原先排第二的元素现在排在了第一
数组名.push()
1 | let fruits = ["Apple", "Orange"]; |
数组名.shift( )
1 | let fruits = ["Apple", "Orange", "Pear"]; |
数组名.shift( ),括号内不填任何东西哦
数组还有另一个用例,就是数据结构 栈。
它支持两种操作:
push
在末端添加一个元素.pop
从末端取出一个元素.
所以新元素的添加和取出都是从“末端”开始的。
栈通常被被形容成一叠卡片:要么在最上面添加卡片,要么从最上面拿走卡片:
对于栈来说,最后放进去的内容是最先接收的,也叫做 LIFO(Last-In-First-Out),即后进先出法则。而与队列相对应的叫做 FIFO(First-In-First-Out),即先进先出。
JavaScript 中的数组既可以用作队列,也可以用作栈。它们允许你从首端/末端来添加/删除元素。
这在计算机科学中,允许这样的操作的数据结构被称为 双端队列(deque)
pop / unshift方法
pop
取出并返回数组的最后一个元素:
1 | let fruits = ["Apple", "Orange", "Pear"]; |
unshift
在数组的首端添加元素:
1 | let fruits = ["Orange", "Pear"]; |
fruits.pop()
和 fruits.at(-1)
都返回数组的最后一个元素,但 fruits.pop()
同时也删除了数组的最后一个元素,进而修改了原数组
数组修改
数组名[索引]=新值
返回值:如果下标不存在,则是新增一个数组元素,并修改了数组长度(尽量避免)
若数组中没有该值则为新增
数组查询
数组名[索引]从0开始
查找不到默认返回undefined
获取最后一个元素
数组名[数组名.length-1]
使用 at 获取最后一个元素
最近新增的特性
这是一个最近添加到 JavaScript 的特性。 旧式浏览器可能需要 polyfills.
假设我们想要数组的最后一个元素。
一些编程语言允许我们使用负数索引来实现这一点,例如 fruits[-1]
。
但在 JavaScript 中这行不通。结果将是 undefined
,因为方括号中的索引是被按照其字面意思处理的。
我们可以显式地计算最后一个元素的索引,然后访问它:fruits[fruits.length - 1]
1 | let fruits = ["Apple", "Orange", "Plum"]; |
有点麻烦,不是吗?我们需要写两次变量名。
幸运的是,这里有一个更简短的语法 fruits.at(-1)
1 | let fruits = ["Apple", "Orange", "Plum"]; |
换句话说,arr.at(i)
:
- 如果
i >= 0
,则与arr[i]
完全相同。 - 对于
i
为负数的情况,它则从数组的尾部向前数
关于length
当我们修改数组的时候,length
属性会自动更新。准确来说,它实际上不是数组里元素的个数,而是最大的数字索引值加一。
例如,一个数组只有一个元素,但是这个元素的索引值很大,那么这个数组的 length
也会很大:
1 | let fruits = []; |
要知道的是我们通常不会这样使用数组。
length
属性的另一个有意思的点是它是可写的。
如果我们手动增加它,则不会发生任何有趣的事儿。但是如果我们减少它,数组就会被截断。该过程是不可逆的,下面是例子:
1 | let arr = [1, 2, 3, 4, 5]; |
所以,清空数组最简单的方法就是:arr.length = 0;
性能
push/pop
方法运行的比较快,而 shift/unshift
比较慢
为什么作用于数组的末端会比首端快呢?让我们看看在执行期间都发生了什么:
1 | fruits.shift(); // 从首端取出一个元素 |
只获取并移除索引 0
对应的元素是不够的。其它元素也需要被重新编号。
shift
操作必须做三件事:
- 移除索引为
0
的元素。 - 把所有的元素向左移动,把索引
1
改成0
,2
改成1
以此类推,对其重新编号。 - 更新
length
属性。
数组里的元素越多,移动它们就要花越多的时间,也就意味着越多的内存操作。
unshift
也是一样:为了在数组的首端添加元素,我们首先需要将现有的元素向右移动,增加它们的索引值。
那 push/pop
是什么样的呢?它们不需要移动任何东西。如果从末端移除一个元素,pop
方法只需要清理索引值并缩短 length
就可以了。
pop
操作的行为:
1 | fruits.pop(); // 从末端取走一个元素 |
pop
方法不需要移动任何东西,因为其它元素都保留了各自的索引。这就是为什么 pop
会特别快。
push
方法也是一样的
数组(splice)方法
从指定位置删除或者增加的数组元素
2个参数是删除
数组名.splice(删除的位置(参考数组索引),删除的个数)
返回值:删除的对应元素
第二个参数不填,默认从索引位置删除到结尾
3个参数是添加
数组名.splice(新增的位置,新增个数(替换操作),新增的数据1,新增的数据2)
返回值:[ ]空数组
sort排序(升序,降序)
数组名.sort()
1 | let arr = [12, 23, 5, 6, 89, 4, 5, 78]; |
1 | arr.sort(function (a, b) { |
函数
函数的基本使用
什么是函数?
一段可以重复使用的代码块,代码块由 { } 包裹
作用:利于代码的重复使用,提高开发效率
其实之前我们使用到的输入,输出框的语句像prompt(‘’) , alert(‘’)就已经使用到了函数,它们是浏览器内置的函数,我们直接调用就可以使用,我们也可以创建自己的函数
使用步骤:
1.定义函数(声明函数)
语法:function
1 | function 函数名() { |
函数名命名与变量一致,使用小驼峰命名法
2.调用函数
函数名( )
函数(代码块)默认不执行,调用的时候才会执行
函数的复用代码和循环重复的代码的区别
循环的代码写完会立即执行,不能很方便的控制执行位置,循环是用来重复执行代码
复用的代码是在需要的地方拿来调用,可以重复的使用
求1-100和案例
1 | //定义函数 |
作用域(scope)
作用域是什么?
变量或是值在代码中生效的范围
作用:管理变量的作用范围,防止发生冲突
1.全局作用域
let
/ const
/ var
声明的变量
作用范围:整个script标签内部,和单独的js文件
2.局部作用域(函数作用域和块级作用域)
函数作用域
作用范围:函数内部生效,函数外部不生效
1 | function fn() { |
块级作用域
作用范围:使用let
或const
在 { } 中定义的变量称为块级作用域
1 | { |
函数内部不声明直接赋值的变量当全局变量,不提倡,乱了套
函数内部的形参当局部变量看
变量的访问原则
在能够访问到的情况下先局部,局部没有往上找全局,全局找不到最后就会报错
就近原则,逐级查找
函数参数
参数:也叫参变量,是一个变量
形参:声明函数时括号里的参数(形式上的参数),[用于接收实参传递过来的数据]
实参:调用函数小括号里的参数(实际上的参数)[向实参传递数据]
传参:把实参的数据传递给形参的执行过程,提供给函数内部使用,简单理解为变量在赋值
我们可以使用参数(也称“函数参数 / 参变量”)来将任意数据传递给函数
在如下示例中,函数有两个参数:from
和 text
1 | function showMessage(from, text) { // 参数:from 和 text |
当函数在 (*)
和 (**)
行中被调用时,给定值(实参)通过传参给到了局部变量 from
和 text
。然后函数使用它们进行计算,最后在页面显示出来
默认值
如果未提供参数,那么其默认值则是 undefined
。
例如,之前提到的函数 showMessage(from, text)
可以只使用一个参数调用:
1 | showMessage("Ann"); |
那不是错误,这样调用将输出 "Ann: undefined"
。这里没有参数 text
,所以程序假定 text === undefined
。
如果我们想在本示例中设定“默认”的 text
,那么我们可以在 =
之后指定它:
1 | function showMessage(from, text = "no text given") { |
现在如果 text
参数未被传递,它将会得到值 "no text given"
。
这里 "no text given"
是一个字符串,但它可以是更复杂的表达式,并且只会在缺少参数时才会被计算和分配。所以,这也是可能的:
1 | function showMessage(from, text = anotherFunction()) { |
默认参数的计算
在 JavaScript 中,每次函数在没带个别参数的情况下被调用,默认参数会被计算出来。
在上面的例子中,每次 showMessage()
不带 text
参数被调用时,anotherFunction()
就会被调用。
形参个数过多, 多余的参数会自动补充 undefined
实参参数过多, 多余的实参参数会被忽略
建议实际开发中形参和实参个数一致
逻辑中断
什么是逻辑中断?
在逻辑运算符中有 $$
与 || ,这两个运算符有另外一个叫法称为短路运算符,这两个运算符的作用是左边如果满足一定条件会中断代码执行,这种行为被称为逻辑短路 / 逻辑中断
(一真则真)逻辑或 ||
中断:如果左边为真,中断右边代码不执行,如果左边为假,则返回右边的值
或运算符 ||
做了如下的事情:
- 从左到右依次计算操作数。
- 处理每一个操作数时,都将其转化为布尔值。如果结果是
true
,就停止计算(逻辑中断),返回这个操作数的初始值。 - 如果所有的操作数都被计算过(也就是,转换结果都是
false
),则返回最后一个操作数。
(一假则假) 逻辑与 $$
中断:如果左边为假,中断右边代码不执行,如果左边为真,则返回右边的值
同理
逻辑运算符在比较时会转为布尔类型,最后返回的结果是原来的值
1 | //1. 声明函数(接收调用者传递的数据) |
函数返回值
语法: return
返回值:函数执行完毕之后的结果
作用:把处理的结果返回给调用者,结束函数
函数不一定要有返回值,例alert ()
函数可以没有返回值,如果没有返回值,结果是undefined
如需拿到返回值进行下一步操作,则需要return返回
举个简单的小栗子:
1 | function sum(a, b) { |
指令 return
可以在函数的任意位置。当执行到达时,函数停止,并将值返回给调用代码(分配给上述代码中的 result
)
在一个函数中可能会出现很多次 return
。例如:
1 | function checkAge(age) { |
只使用 return
但没有返回值也是可行的。但这会导致函数立即退出
例如:
1 | function showMovie(age) { |
在上述代码中,如果 checkAge(age)
返回 false
,那么 showMovie
将不会运行到 alert
。
空值的 return
或没有 return
的函数返回值为 undefined
如果函数无返回值,它就会像返回 undefined
一样:
1 | function doNothing() { /* 没有代码 */ } |
空值的 return
和 return undefined
等效:
1 | function doNothing() { |
不要在 return
与返回值之间添加新行
对于 return
的长表达式,可能你会很想将其放在单独一行,如下所示:
1 | return |
但这不行,因为 JavaScript 默认会在 return
之后加上分号,上面这段代码和下面这段代码运行流程相同:
1 | return; |
因此,实际上它的返回值变成了空值。
如果我们想要将返回的表达式写成跨多行的形式,那么应该在 return
的同一行开始写此表达式。或者至少按照如下的方式放上左括号:
1 | return ( |
然后它就能像我们预想的那样正常运行了
1 | function showMovie(age) { |
封装获取数组最大值函数案例
1 | // 封装求数组最大值函数 |
函数命名
函数就是行为(action)。所以它们的名字通常是动词。它应该简短且尽可能准确地描述函数的作用。这样读代码的人就能清楚地知道这个函数的功能
一种普遍的做法是用动词前缀来开始一个函数,这个前缀模糊地描述了这个行为。团队内部必须就前缀的含义达成一致。
例如,以 "show"
开头的函数通常会显示某些内容。
函数以 XX 开始……
"get…"
—— 返回一个值,"calc…"
—— 计算某些内容,"create…"
—— 创建某些内容,"check…"
—— 检查某些内容并返回 boolean 值,等。
有了前缀,只需瞥一眼函数名,就可以了解它的功能是什么,返回什么样的值。
一个函数 —— 一个行为
一个函数应该只包含函数名所指定的功能,而不是做更多与函数名无关的功能。
两个独立的行为通常需要两个函数,即使它们通常被一起调用(在这种情况下,我们可以创建第三个函数来调用这两个函数)。
有几个违反这一规则的例子:
getAge
—— 如果它通过alert
将 age 显示出来,那就有问题了(只应该是获取)。createForm
—— 如果它包含修改文档的操作,例如向文档添加一个表单,那就有问题了(只应该创建表单并返回)。checkPermission
—— 如果它显示access granted/denied
消息,那就有问题了(只应执行检查并返回结果)。
这些例子假设函数名前缀具有通用的含义。你和你的团队可以自定义这些函数名前缀的含义,但是通常都没有太大的不同。无论怎样,你都应该对函数名前缀的含义、带特定前缀的函数可以做什么以及不可以做什么有深刻的了解。所有相同前缀的函数都应该遵守相同的规则。并且,团队成员应该形成共识。
非常短的函数命名
常用的函数有时会有非常短的名字。
例如,jQuery 框架用 $
定义一个函数。LoDash 库的核心函数用 _
命名。
这些都是例外,一般而言,函数名应简明扼要且具有描述性。
函数分类
具名函数
带有名字的函数
1 | function fn( ) { |
匿名函数
没有名字的函数
1 | function () { |
匿名函数的两种使用方式
函数表达式
在 JavaScript 中,函数不是“神奇的语言结构”,而是一种特殊的值,这个值可以被赋值给一个变量,通过变量名来调用,这个就称之为函数表达式
1 | let sayHi = function() { |
在这里,函数被创建并像其他赋值一样,被明确地分配给了一个变量。不管函数是被怎样定义的,都只是一个存储在变量 sayHi
中的值。
我们还可以用 alert
打印这个变量值:
1 | let sayHi = function() { |
匿名函数和具名函数使用方式一致
立即执行函数( IIFE)自执行函数
方式一
方式二
作用:防止全局变量冲突1.立即执行函数使用时,所有的语法需要使用分号隔开
2.立即执行函数和普通函数没有什么区别
3.立即执行函数也属于函数一类,也是一个数据,属于引用数据类型
回调函数
把一个函数作为参数传递给另外一个函数
我们写一个包含三个参数的函数 ask(question, yes, no)
:
1 | question |
关于问题的文本
1 | yes |
当回答为 “Yes” 时,要运行的脚本
1 | no |
当回答为 “No” 时,要运行的脚本
函数需要提出 question
(问题),并根据用户的回答,调用 yes()
或 no()
:
1 | function ask(question, yes, no) { |
在实际开发中,这样的的函数是非常有用的。实际开发与上述示例最大的区别是,实际开发中的函数会通过更加复杂的方式与用户进行交互,而不是通过简单的 prompt。在浏览器中,这样的函数通常会绘制一个漂亮的提问窗口。但这是另外一件事了。
ask
的两个参数值 showOk
和 showCancel
可以被称为 回调函数 或简称 回调
主要思想是我们传递一个函数,并期望在稍后必要时将其“回调”。在我们的例子中,showOk
是回答 “yes” 的回调,showCancel
是回答 “no” 的回调
我们可以用函数表达式对同样的函数进行大幅简写:
1 | function ask(question, yes, no) { |
这里直接在 ask(...)
调用内进行函数声明。这两个函数没有名字,所以叫 匿名函数。这样的函数在 ask
外无法访问(因为没有对它们分配变量),不过这正是我们想要的。
这样的代码在我们的脚本中非常常见,这正符合 JavaScript 语言的思想。
一个函数是表示一个“行为”的值
字符串或数字等常规值代表 数据。
函数可以被视为一个 行为(action)。
我们可以在变量之间传递它们,并在需要时运行。
函数声明和函数表达式之间的主要区别:
首先是语法:如何通过代码对它们进行区分
函数声明:在主代码流中声明为单独的语句的函数
1 | // 函数声明 |
函数表达式:在一个表达式中或另一个语法结构中创建的函数。下面这个函数是在赋值表达式 =
右侧创建的:
1 | // 函数表达式 |
更细微的差别是,JavaScript 引擎会在 什么时候 创建函数。
函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用。
一旦代码执行到赋值表达式 let sum = function…
的右侧,此时就会开始创建该函数,并且可以从现在开始使用(分配,调用等)。
函数声明则不同。
在函数声明被定义之前,它就可以被调用。
例如,一个全局函数声明对整个脚本来说都是可见的,无论它被写在这个脚本的哪个位置。
这是内部算法的原故。当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数。我们可以将其视为“初始化阶段”。
在处理完所有函数声明后,代码才被执行。所以运行时能够使用这些函数。
箭头函数
1 | let func = (arg1, arg2, ...argN) => expression |
……这里创建了一个函数 func
,它接受参数 arg1..argN
,然后使用参数对右侧的 expression
求值并返回其结果。
换句话说,它是下面这段代码的更短的版本:
1 | let func = function(arg1, arg2, ...argN) { |
让我们来看一个具体的例子:
1 | let sum = (a, b) => a + b; |
可以看到 (a, b) => a + b
表示一个函数接受两个名为 a
和 b
的参数。在执行时,它将对表达式 a + b
求值,并返回计算结果。
如果我们只有一个参数,还可以省略掉参数外的圆括号,使代码更短。
例如:
1 | let double = n => n * 2; |
如果没有参数,括号将是空的(但括号应该保留):
1 | let sayHi = () => alert("Hello!"); |
箭头函数可以像函数表达式一样使用
例如动态创建一个函数
1 | let age = prompt("What is your age?", 18); |
多行的箭头函数
上面的例子从 =>
的左侧获取参数,然后使用参数计算右侧表达式的值。
但有时我们需要更复杂一点的东西,比如多行的表达式或语句。这也是可以做到的,但是我们应该用花括号括起来。然后使用一个普通的 return
将需要返回的值进行返回。
就像这样:
1 | let sum = (a, b) => { // 花括号表示开始一个多行函数 |