js:变量;运算符;流程控制;类型检测;
变量声明
#命名规则
JS 中的变量是弱类型可以保存所有类型的数据,即变量没有类型而值有类型。变量名以字母、$、_ 开始,后跟字母、数字、_。
下面都是合法的命名
let name = "houdunren";
let $ = "hdcms";
JS 语言关键字不能用来做变量名,比如 true、if、while、class 等。
let class = 'houdunren';
#变量声明
可以使用多种方式定义变量比如 var、let 等(后面作用域章节会再讨论变量)。
let name = "houdunren";
以上代码是声明和赋值的结合
let name;
name = "houdunren";
变量的其他细节使用会在函数、对象等章节中体验
使用, 可以同时声明多个变量
let n = 2,
f = 3;
console.log(f);
下面演示了变量可以更换不同类型的数据
let hd = "houdunren";
console.log(typeof hd);
hd = 18;
console.log(typeof hd);
#弱类型
在 JS 中变量类型由所引用的值决定
var web = "hdcms";
console.log(typeof web); //string
web = 99;
console.log(typeof web); //number
web = {};
console.log(typeof web); //object
#变量提升
解析器会先解析代码,然后把声明的变量的声明提升到最前,这就叫做变量提升。
下面代码在解析过程中发现while不能做为变量名,没有到执行环节就出错了,这是一个很好的解析过程的体验。
var web = 'houdunren';
console.log(web);
let while = 'hdcms'; //Uncaught SyntaxError: Unexpected token 'while'
使用 var 声明代码会被提升到前面
console.log(a); //undefined
var a = 1;
console.log(a); //1
//以上代码解析器执行过程如下
var a;
console.log(a); //1
a = 1;
console.log(a); //1
下面是 if(false) 中定义的 var 也会发生变量提升,注释掉if 结果会不同
var web = "houdunren";
function hd() {
if (false) {
var web = "后盾人";
}
console.log(web);
}
hd();
使用 var 定义的代码,声明会被提升到前面,赋值还在原位置
console.log(hd);
var hd = "后盾人";
//以上代码解析器执行过程如下
var hd;
console.log(hd); //后盾人
hd = "后盾人";
#TDZ
TDZ 又称暂时性死区,指变量在作用域内已经存在,但必须在let/const声明后才可以使用。
TDZ 可以让程序保持先声明后使用的习惯,让程序更稳定。
- 变量要先声明后使用
- 建议使用 let/const 而少使用 var
使用let/const 声明的变量在声明前存在临时性死区(TDZ)使用会发生错误
console.log(x); // Cannot access 'x' before initialization
let x = 1;
在run函数作用域中产生 TDZ,不允许变量在未声明前使用。
hd = "houdunren";
function run() {
console.log(hd);
let hd = "hdcms";
}
run();
下面代码 b 没有声明赋值不允许直接使用
function hd(a = b, b = 3) {}
hd(); //Cannot access 'b' before initialization
因为 a 已经赋值,所以 b 可以使用 a 变量,下面代码访问正常
function hd(a = 2, b = a) {}
hd();
#块作用域
#共同点
var/let/const共同点是全局作用域中定义的变量,可以在函数中使用
var hd = "hdcms";
function show() {
return hd;
}
console.log(show());
函数中声明的变量,只能在函数及其子函数中使用
function hd() {
var web = "后盾人";
function show() {
console.log(web);
}
show(); //子函数结果: 后盾人
console.log(web); //函数结果: 后盾人
}
hd();
console.log(web); //全局访问: hd is not defined
函数中声明的变量就像声明了私有领地,外部无法访问
var web = "hdcms.com";
function hd() {
var web = "houdunren.com";
console.log(web); //houdunren.com
}
hd();
console.log(web); //hdcms.com
#var
使用 var 声明的变量存在于最近的函数或全局作用域中,没有块级作用域的机制。
没有块作用域很容易污染全局,下面函数中的变量污染了全局环境
function run() {
web = "houdunren";
}
run();
console.log(web); //houdunren
没有块作用作用域时 var 也会污染全局
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(i);
使用let有块作用域时则不会
let i = 100;
for (let i = 0; i < 6; i++) {
console.log(i);
}
console.log(i);
下例中体验到 var 没有块作用域概念, do/while 定义的变量可以在块外部访问到
var num = 0;
function show() {
var step = 10;
do {
var res = 0;
console.log((num = step++));
res = num;
} while (step < 20);
console.log(`结果是${res}`);
}
show();
var 全局声明的变量也存在于 window对象中
var hd = "houdunren";
console.log(window.hd); //houdunren
以往没有块任用时使用立即执行函数模拟块作用域
(function () {
var $ = (this.$ = {});
$.web = "后盾人";
}).bind(window)();
console.log($.web);
有了块作用域后实现就变得简单多了
{
let $ = (window.$ = {});
$.web = "后盾人";
}
console.log($.web);
#let
与 var 声明的区别是 let/const 拥有块作用域,下面代码演示了块外部是无法访问到let声明的变量。
- 建议将
let在代码块前声明 - 用逗号分隔定义多个
let存在块作用域特性,变量只在块域中有效
if (true) {
let web = "hdcms",
url = "houdunren.com";
console.log(web); //hdcms
}
console.log(web); //web is not defined
块内部是可以访问到上层作用域的变量
if (true) {
let user = "大叔";
(function () {
if (true) {
console.log(`这是块内访问:${user}`);
}
})();
}
console.log(user);
每一层都是独立作用域,里层作用域可以声明外层作用域同名变量,但不会改变外层变量
function run() {
hd = "houdunren";
if (true) {
let hd = "hdcms";
console.log(hd); //hdcms
}
console.log(hd); //houdunren
}
run();
#const
使用 const 用来声明常量,这与其他语言差别不大,比如可以用来声明后台接口的 URI 地址。
- 常量名建议全部大写
- 只能声明一次变量
- 声明时必须同时赋值
- 不允许再次全新赋值
- 可以修改引用类型变量的值
- 拥有块、函数、全局作用域
常量不允许全新赋值举例
try {
const URL = "https://www.houdunren.com";
URL = "https://www.hdcms.com"; //产生错误
} catch (error) {
throw new Error(error);
}
改变常量的引用类型值
const INFO = {
url: "https://www.houdunren.com",
port: "8080",
};
INFO.port = "443";
console.log(INFO);
下面演示了在不同作用域中可以重名定义常量
const NAME = "后盾人";
function show() {
const NAME = "大叔";
return NAME;
}
console.log(show());
console.log(NAME);
#重复定义
使用 var 可能造成不小心定义了同名变量
//优惠价
var price = 90;
//商品价格
var price = 100;
console.log(`商品优惠价格是:${price}`);
使用let 可以避免上面的问题,因为 let 声明后的变量不允许在同一作用域中重新声明
let web = "houdunren.com";
let web = "后盾人"; //Identifier 'web' has already been declared
不同作用域可以重新声明
let web = "houdunren.com";
if (true) {
let web = "后盾人"; //Identifier 'web' has already been declared
}
但可以改变值这是与 const 不同点
let price = 90;
price = 88;
console.log(`商品价格是:${price}`);
let 全局声明的变量不存在于 window对象中,这与var声明不同
let hd = "hdcms";
console.log(window.hd); //undefined
#Object.freeze
如果冻结变量后,变量也不可以修改了,使用严格模式会报出错误。
"use strict";
const INFO = {
url: "https://www.houdunren.com",
port: "8080",
};
Object.freeze(INFO);
INFO.port = "443"; //Cannot assign to read only property
console.log(INFO);
VM143:7 Uncaught TypeError: Cannot assign to read only property 'port' of object '#<Object>'
at <anonymous>:7:11
#传值与传址
基本数据类型指数值、字符串等简单数据类型,引用类型指对象数据类型。
类型的详细介绍会在后面章节讲解
基本类型复制是值的复制,互相不受影响。下例中将 a 变量的值赋值给 b 变量后,因为基本类型变量是独立的所以 a 的改变不会影响 b 变量的值。
let a = 100;
let b = a;
a = 200;
console.log(b);
对于引用类型来讲,变量保存的是引用对象的指针。变量间赋值时其实赋值是变量的指针,这样多个变量就引用的是同一个对象。
let a = {
web: "后盾人",
};
let b = a;
a.web = "hdcms";
console.log(b);
#undefined
对声明但未赋值的变量返回类型为 undefined 表示值未定义。
let hd;
console.log(typeof hd);
对未声明的变量使用会报错,但判断类型将显示 undefined。

console.log(typeof houdunren);
console.log(houdunren);
我们发现未赋值与未定义的变量值都为 undefined ,建议声明变量设置初始值,这样就可以区分出变量状态了。
函数参数或无返回值是为undefined
function hd(web) {
console.log(web); //undefined
return web;
}
console.log(hd()); //undefined
#null
null 用于定义一个空对象,即如果变量要用来保存引用类型,可以在初始化时将其设置为 null
var hd = null;
console.log(typeof hd);
#严格模式
严格模式可以让我们及早发现错误,使代码更安全规范,推荐在代码中一直保持严格模式运行。
主流框架都采用严格模式,严格模式也是未来 JS 标准,所以建议代码使用严格模式开发
#基本差异
变量必须使用关键词声明,未声明的变量不允许赋值
"use strict";
url = "houdunren.com"; //url is not defined
强制声明防止污染全局
"use strict";
function run() {
web = "houdunren";
}
run();
console.log(web); //houdunren
关键词不允许做变量使用
"use strict";
var public = "houdunren.com";
变量参数不允许重复定义
"use strict";
//不允许参数重名
function hd(name, name) {}
单独为函数设置严格模式
function strict() {
"use strict";
return "严格模式";
}
function notStrict() {
return "正常模式";
}
为了在多文件合并时,防止全局设置严格模式对其他没使用严格模式文件的影响,将脚本放在一个执行函数中。
(function () {
"use strict";
url = "houdunren.com";
})();
#解构差异
非严格模式可以不使用声明指令,严格模式下必须使用声明。所以建议使用 let 等声明。
// "use strict";
({ name, url } = { name: "后盾人", url: "houdunren.com" });
console.log(name, url);
运算符
#赋值运算符
使用 = 进行变量赋值
let url = "houdunren.com";
#算术运算符
包括以下几种算术运算符。
| 运算符 | 说明 |
|---|---|
| * | 乘法 |
| / | 除法 |
| + | 加法 |
| - | 减法 |
| % | 取余数 |
let a = 5,
b = 3;
console.log(a * b); //15
console.log(a % b); //2
#复合运算符
可以使用 *=、/=、+=、-=、%= 简写算术运算。即 n*=2 等同于 n=n*2。
let n = 2;
n *= 2;
console.log(n);
对变量加减相应数值。
let n = 2;
n += 3;
console.log(n); //0
n -= 5;
console.log(n); //5
n+=3 是 n=n+3 的简写形式
#一元运算符
#前置操作
前置操作会在表达式最先执行。
let n = 1;
++n;
console.log(n);
--n;
console.log(n);
++n 就是 n=n+1 的简写形式。
使用后置操作符,++n 会在最先执行,所以 f 的结果是 33。
let n = 2;
let f = 30 + ++n;
console.log(f);
#后置操作
后置操作会在表达式最后执行。
let n = 1;
n++;
console.log(n);
使用后置操作符,n++ 会在最后执行,所以 f 的结果是 32。
let n = 2;
let f = 30 + n++;
console.log(f);
参与数学计算
let a = 1;
b = a++ + 2;
console.log(b); //3
#比较运算符
| 运算符 | 说明 |
|---|---|
| > | 大于 |
| < | 小于 |
| >= | 大于或等于 |
| <= | 小于等于 |
| == | 强制类型转换比较 |
| === | 不强制类型转换比较 |
下面来体验不同类型的比较结果
let a = 1,
b = 2,
c = "1";
console.log(a < b); //true
console.log(a == b); //false
console.log(a == c); //true
console.log(a === c); //false
console.log(a == true); //true
console.log(a === true); //false
以下示例不允许年龄超过 90 岁
<input type="text" name="age" />
<span id="msg"></span>
<script>
let span = document.querySelector("#msg");
document
.querySelector('[name="age"]')
.addEventListener("keyup", function() {
span.innerHTML = this.value >= 90 ? "年龄不能超过90岁" : "";
});
</script>
#逻辑运算符
#逻辑与
使用 && 符号表示逻辑与,指符号两端都为 true 时表达式结果为 true。
let a = true,
b = true;
if (a && b) {
console.log("表达式成立");
}
#逻辑或
使用 || 符号表示逻辑或,指符号左右两端有一方为 true,表达式即成立。
let a = true,
b = false;
if (a || b) {
console.log("表达式成立");
}
#逻辑非
使用 ! 符号表示逻辑非,即原来是 true 转变为 false,反之亦然。
let a = true,
b = false;
if (a && !b) {
console.log("表达式成立");
}
#优先级
下列中因为 && 的优先级高所以结果是 true。
console.log(true || false && false);
VM235:1 true
console.log( false && false || true);
VM235:1 true
可以使用 () 来提高优先级
console.log((true || false) && false); //false
#密码比对实例

<input type="text" name="password" />
<input type="text" name="confirm_password" />
<br />
<span name="msg"></span>
<script>
function queryByName(name) {
return document.querySelector(`[name='${name}']`);
}
let inputs = document.querySelectorAll(
"[name='password'],[name='confirm_password']"
);
[...inputs].map(item => {
item.addEventListener("keyup", () => {
let msg = "";
if (
queryByName("password").value !=
queryByName("confirm_password").value ||
queryByName("password").value.length < 5
) {
msg = "两次密码不一致或密码长度错误";
}
queryByName("msg").innerHTML = msg;
});
});
#短路运算
下例中 a 为真值,就已经知道结果了就不会再判断 f 的值了。
let a = true,
f = false;
console.log(a || f);
同理当 f 值为假时,就已经可以判断 && 的结果了,就没有判断 a的必要了。
let a = true,
f = false;
console.log(f && a);
使用短路特性赋值
let sex = prompt("你的性别是?") || "保密";
console.log(sex);
当 opt.url 没有值时,使用短路特性设置 url 的值
let opt = {
url: "",
};
function getUrl(opt) {
opt.url = "houdunren.com";
}
opt.url || getUrl(opt);
console.log(opt.url);
#实例操作
下面的例子在用户输入表单项并接收协议后才可提交
<body>
<form action="https://www.houdunren.com" id="form">
用户名: <input type="text" name="username" />
<hr />
<input type="checkbox" name="copyright" /> 接收协议
<hr />
<input type="submit" />
</form>
</body>
<script>
function query(el) {
return document.querySelector(el);
}
query("#form").addEventListener("submit", function(event) {
let username = query('input[name="username"]').value;
let copyright = query('input[name="copyright"]').checked;
console.log(!!username);
if (!username || copyright === false) {
alert("请填写用户名并接受协议");
event.preventDefault();
}
});
</script>
#流程控制
#if
当条件为真时执行表达式代码块。
let state = true;
if (true) {
console.log("表达式成立");
}
如果只有一条代码块,可以不用写 {}
let state = true;
if (true) console.log("表达式成立");
console.log("一直都显示的内容");
#if/else
下面是使用多条件判断密码强度的示例

<body>
<input type="password" name="title" />
<span></span>
</body>
<script>
let input = document.querySelector("[name='title']");
input.addEventListener("keyup", function() {
let length = this.value.length;
let msg;
if (length > 10) {
msg = "密码已经无敌了";
} else if (length > 6) {
msg = "密码安全性中级";
} else {
msg = "这密码,要完的节奏";
}
document.querySelector("span").innerHTML = msg;
});
</script>
#三元表达式
是针对 if 判断的简写形式。
let n = true ? 1 : 2;
console.log(n); //1
let f = true ? (1 == true ? "yes" : "no") : 3;
console.log(f); // yes
下面是创建 DIV 元素的示例,使用三元表达式设置初始值
function div(options = {}) {
let div = document.createElement("div");
div.style.width = options.width ? options.width : "100px";
div.style.height = options.height ? options.height : "100px";
div.style.backgroundColor = options.bgcolor ? options.bgcolor : "red";
document.body.appendChild(div);
}
div();
#switch
可以将 switch 理解为 if 的另一种结构清晰的写法。
- 如果表达式等于
case中的值,将执行此case代码段 break关键字会终止switch的执行- 没有任何
case匹配时将执行default代码块 - 如果
case执行后缺少 break 则接着执行后面的语句
let name = "视频";
switch (name) {
case "产品":
console.log("hdcms.com");
break;
case "视频":
console.log("houdunren.com");
break;
default:
console.log("houdunwang.com");
}
case 合用示例
let error = "warning";
switch (error) {
case "notice":
case "warning":
console.log("警告或提示信息");
break;
case "error":
console.log("错误信息");
}
在switch 与 case 都可以使用表达式
function message(age) {
switch (true) {
case age < 15:
console.log("儿童");
break;
case age < 25:
console.log("青少年");
break;
case age < 40:
console.log("青年");
break;
case age < 60:
console.log("中年");
break;
case age < 100:
console.log("老年");
break;
default:
console.log("年龄输出错误");
}
}
message(10);
下面例子缺少 break 后,会接着执行后面的 switch 代码。
switch (1) {
case 1:
console.log(1);
case 2:
console.log(2);
default:
console.log("default");
}
#while
循环执行语句,需要设置跳出循环的条件否则会陷入死循环状态。下面是循环输出表格的示例。
let row = 5;
document.write(`<table border="1" width="100">`);
while (row-- != 0) {
document.write(`<tr><td>${row}</td></tr>`);
}
document.write(`</table>`);
#do/while
后条件判断语句,无论条件是否为真都会先进行循环体。
下面通过循环输出三角形示例,要注意设置循环跳出的时机来避免死循环。
*
**
***
****
*****
function hd(row = 5) {
let start = 0;
do {
let n = 0;
do {
document.write("*");
} while (++n <= start);
document.write("<br/>");
} while (++start <= row);
}
hd();
#for
可以在循环前初始化初始计算变量。下面是使用for 打印倒三角的示例
**********
*********
********
*******
******
*****
****
***
**
*
for (let i = 10; i > 0; i--) {
for (let n = 0; n < i; n++) {
document.write('*');
}
document.write("<br/>");
}
下面是使用循环制作杨辉三角的案例

*
***
*****
*******
*********
for (let i = 1; i <= 5; i++) {
for (let n = 5 - i; n > 0; n--) {
document.write('^');
}
for (let m = i * 2 - 1; m > 0; m--) {
document.write('*');
}
document.write("<br/>");
}
for 的三个参数可以都省略或取几个
let i = 1;
for (; i < 10; ) {
console.log(i++);
}
#break/continue
break 用于退出当前循环,continue 用于退出当前循环返回循环起始继续执行。
获取所有偶数,所有奇数使用 continue 跳过
for (let i = 1; i <= 10; i++) {
if (i % 2) continue;
console.log(i);
}
获取三个奇数,超过时使用 break退出循环
let count = 0,
num = 3;
for (let i = 1; i <= 10; i++) {
if (i % 2) {
console.log(i);
if (++count == num) break;
}
}
#label
标签(label) 为程序定义位置,可以使用continue/break跳到该位置。
下面取i+n 大于 15 时退出循环
houdunren: for (let i = 1; i <= 10; i++) {
hdcms: for (let n = 1; n <= 10; n++) {
if (n % 2 != 0) {
continue hdcms;
}
console.log(i, n);
if (i + n > 15) {
break houdunren;
}
}
}
#for/in
用于遍历对象的所有属性,for/in主要用于遍历对象,不建议用来遍历数组。
遍历数组操作
let hd = [
{ title: "第一章 走进JAVASCRIPT黑洞", lesson: 3 },
{ title: "ubuntu19.10 配置好用的编程工作站", lesson: 5 },
{ title: "媒体查询响应式布局", lesson: 8 },
];
document.write(`
<table border="1" width="100%">
<thead><tr><th>标题</th><th>课程数</th></thead>
`);
for (let key in hd) {
document.write(`
<tr>
<td>${hd[key].title}</td>
<td>${hd[key].lesson}</td>
</tr>
`);
}
document.write("</table>");
遍历对象操作
let info = {
name: "后盾人",
url: "houdunren.com",
};
for (const key in info) {
if (info.hasOwnProperty(key)) {
console.log(info[key]);
}
}
遍历 window 对象的所有属性
for (name in window) {
console.log(window[name]);
}
#for/of
用来遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构。
与 for/in 不同的是 for/of 每次循环取其中的值而不是索引。
后面在讲到
遍历器章节后大家会对 for/of 有更深的体会
let arr = [1, 2, 3];
for (const iterator of arr) {
console.log(iterator);
}
遍历字符串
let str = "houdunren";
for (const iterator of str) {
console.log(iterator);
}
使用迭代特性遍历数组(后面章节会介绍迭代器)
const hd = ["hdcms", "houdunren"];
for (const [key, value] of hd.entries()) {
console.log(key, value); //这样就可以遍历了
}
使用for/of 也可以用来遍历 DOM 元素
<body>
<ul>
<li></li>
<li></li>
</ul>
</body>
<script>
let lis = document.querySelectorAll("li");
for (const li of lis) {
li.addEventListener("click", function() {
this.style.backgroundColor = "red";
});
}
</script>
类型检测
JS 提供了非常丰富的数据类型,开发者要学会使用最适合的数据类型处理业务 。
#typeof
typeof 用于返回以下原始类型
- 基本类型:number/string/boolean
- function
- object
- undefined
可以使用 typeof 用于判断数据的类型
let a = 1;
console.log(typeof a); //number
let b = "1";
console.log(typeof b); //string
//未赋值或不存在的变量返回undefined
var hd;
console.log(typeof hd);
function run() {}
console.log(typeof run); //function
let c = [1, 2, 3];
console.log(typeof c); //object
let d = { name: "houdunren.com" };
console.log(typeof d); //object
#instanceof
**
instanceof
** 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
也可以理解为是否为某个对象的实例,typeof不能区分数组,但instanceof则可以。
后面章节会详细介绍原型链
let hd = [];
let houdunren = {};
console.log(hd instanceof Array); //true
console.log(houdunren instanceof Array); //false
let c = [1, 2, 3];
console.log(c instanceof Array); //true
let d = { name: "houdunren.com" };
console.log(d instanceof Object); //true
function User() {}
let hd = new User();
console.log(hd instanceof User); //true
#值类型与对象
下面是使用字面量与对象方法创建字符串,返回的是不同类型。
let hd = "houdunren";
let cms = new String("hdcms");
console.log(typeof hd, typeof cms); //string object
只有对象才有方法使用,但在JS中也可以使用值类型调用方法,因为它会在执行时将值类型转为对象。
let hd = "houdunren";
let cms = new String("hdcms");
console.log(hd.length); //9
console.log(cms.length); //5