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