给JS扫扫盲——读JS红宝书(第三章)

第3章 语言基础

一、语法

1. 区分大小写

ECMAScript中一切区分大小写。无论是变量、函数名还是操作符,都区分大小写

举个例子:变量test和变量Test是两个不同的变量

2.标识符

(1)第一个字符必须是字母、下划线(_)或美元符号($)

(2)剩下的其他字符也可以是字母、下划线、美元符号或数字

ECMAScript标识符使用驼峰大小写形式,也就是第一个单词首字母小写,后面每个单词首字母大写。

注意: 关键字、保留字、true 、false 和null不能作为标识符

举几个例子:firstSecond myCar doSomethingImportant

我们找两道题看一下

1.以下 ECMAScript 变量命名格式正确的是()

A _125doll  B 1207A  C -dollor  D var

答案当然是A

解析:选项B,不能以数字开头;选项C,不能以-开头;选项D,变量命名不能是关键字

2.下列变量名合法的是()

A 5show  B return  C $user  D var

答案当然是C

解析:选项A,不能以数字开头;选项B,变量名不能是关键字;选项D,变量命名不能是关键字

3.注释

注释这里就不多提了,注意一下HTML、CSS、JS里注释的区分吧

HTML注释:

<!-- 我是HTML注释 -->

CSS注释:

/* 我是CSS注释 */

JS注释:

// 我是JS注释

我们找道题看一下

1.下述可正确注释html代码的有?

A	// <div></div>
B	# <div></div>
C	<!-- <div></div> -->
D	/* <div></div> */

答案是C,这道题印象中做的时候出的是不定项,如果注释记混了的话,很容易错,考察的是HTML注释记没记住。

4.严格模式

严格模式这里也不太多提,讲一下开启严格模式的方法,如果想练习的话,可以查看严格模式文档练习。

链接: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode.

对整个脚本启用严格模式,在脚本开头加上这一行

"use strict";

也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放 到函数体开头即可

function doSomething() {
"use strict";
// 函数体
}

5.语句

ECMAScript中的语句以分号结尾。省略分号意味着由解析器确定语句 在哪里结尾,如下面的例子所示:

let sum = a + b // 没有分号也有效,但不推荐
let diff = a - b; // 加分号有效,推荐

语句即使不加分号,也会生效,但是要养成加分号的习惯。

比如:加分号有助于防止省略造成的问题,比如可以避免输入内容不完整。此外,加分号也便于开发者通过删除空行来压缩代码,如果没有结尾的分号,只删除空行,则会导致语法错误。加分号也有助于在某些情况下提升性能,因为解 析器会尝试在合适的位置补上分号以纠正语法错误。

再说说像if语句需要写代码块的:

// 有效,但容易导致错误,应该避免
if (test)
console.log(test);
// 推荐
if (test) {
console.log(test);
}

即使只有一条语句,也要养成写成代码块的习惯,能让我们的代码结构变得非常清晰,在需要修改代码时也可以减少出错的可能性。

二、关键字与保留字

就多看看记忆一下吧,挺多的

关键字:

break 	do 	in 	typeof
case 	else 	instanceof 	var
catch 	export 		new 	void
class 	extends 	return 	while
const 	finally 	super 	with
continue 	for 	switch 	yield
debugger 	function 	this
default 	if	 throw
delete 	import 	try

规范中也描述了一组未来的保留字 ,同样不能用作标识符或属性名。虽然保留字在语言中没有特定用途,但它们是保留给将来做关键字用的。

始终保留:
enum

严格模式下保留:
implements	 package 	public
interface 	protected 	static
let 	private

模块代码中保留:
await

这些词汇不能用作标识符,但现在还可以用作对象的属性名。一般来说,最好还是不要使用关键字和保留字作为标识符和属性名,以确保兼容过去和未来的ECMAScript版本。

三、变量

有三个关键字可以声明变量:var 、const 和let

1.var 关键字

定义变量可以使用var,后面跟变量名

var message;

定义了一个message变量,它可以保存任意类型的值

在不初始化变量的情况下,变量会保存一个特殊值undefined

我们可以在定义变量的时候同时给它进行赋值:

var message = "hi";

之后我们可以改变保存的值和保存值的类型(合法,但不推荐)

var message = "hi";
message = 100; // 合法,但不推荐
(1)var 声明作用域

使用var 操作符定义的变量会成为包含它的函数的局部变量。

使用var 在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:

function test() {
var message = "hi"; // 局部变量
}
test();
console.log(message); // 出错!

我们来。。。实际运行一哈

在这里插入图片描述

也确实是报错了,变量确实在函数退出时销毁了

但是吧,不用var声明,直接写message = "hi" 它就相当于创建了一个全局变量

function test() {
message = "hi"; // 全局变量
}
test();
console.log(message); // "hi"

来实际运行一哈,感受一下js的神奇机制

在这里插入图片描述

去掉之前的var 操作符之后,message 就变成了全局变量。只要调用一次函数test() ,就会定义这个变量,并且可以在函数外部访问到。

不推荐通过这样的方式定义全局变量,代码会变得难以维护,在严格模式下,会报错:ReferenceError

如果需要定义多个变量,可以在一条语句中用逗号分隔每个变量

var message = "hi",
found = false,
age = 29;

在严格模式下,不能定义名为eval 和arguments 的变量,否则会导致语法错误。

(2)var 声明提升

使用var 时,下面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部:

function foo() {
console.log(age);
var age = 26;
}
foo(); // undefined

ECMAScript运行时把它看成等价于如下代码:

function foo() {
var age;
console.log(age);
age = 26;
}
foo(); // undefined

我们来实际运行一哈:

在这里插入图片描述

2.let 声明

let 声明的范围是块作用域,而var 声明的范围是函数作用域。

if (true) {
var name = 'Matt';
 console.log(name); // Matt
}
console.log(name); // Matt
if (true) {
let age = 26;
console.log(age); // 26
}
console.log(age); // ReferenceError: age没有定义

来实际运行一哈

在这里插入图片描述

age 变量之所以不能在if 块外部被引用,是因为它的作用域仅限于该块内部。块作用域是函数作用域的子集,因此适用于var 的作用域限制同样也适用于let 。

let 也不允许同一个块作用域中出现冗余声明。这样会导致报错:

var name;
var name;
let age;
let age; // SyntaxError;标识符age已经声明过了

来实际运行一哈

在这里插入图片描述

下面再看一个例子:

var name = 'Nicholas';
console.log(name); // 'Nicholas'
if (true) {
var name = 'Matt';
console.log(name); // 'Matt'
}
let age = 30;
console.log(age); // 30
if (true) {
let age = 26;
console.log(age); // 26
}

来实际运行一哈

在这里插入图片描述

嵌套使用相同的标识符不会报错,而这是因为同一个块中没有 重复声明

对声明冗余报错不会因混用let 和var 而受影响。

var name;
let name; // SyntaxError
let age;
var age; // SyntaxError

在这里插入图片描述

两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在

(1)暂时性死区

let 与var 的另一个重要的区别,就是let 声明的变量不会在作用域中被提升。

我们看下面的例子:

// name会被提升
console.log(name); // undefined
var name = 'Matt';
// age不会被提升
console.log(age); // ReferenceError:age没有定义
let age = 26;

实际运行一哈

在这里插入图片描述

在解析代码时,JavaScript引擎也会注意出现在块后面的let 声明,只不过在此之前不能以任何方式来引用未声明的变量。在let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError 。

(2)全局声明

使用let 在全局作用域中声明的变量不会成为window 对象的属性

来看下面的例子:

var name = 'Matt';
console.log(window.name); // 'Matt'
let age = 26;
console.log(window.age); // undefined

来实际运行一哈:

在这里插入图片描述

(3)条件声明

在使用var 声明变量时,由于声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。因为let 的作用域是块,所以不可能检查前面是否已经使用let 声明过同名变量,同时也就不可能在没有声明的情况下声明它。

<script>
var name = 'Nicholas';
let age = 26;
</script>
<script>
// 假设脚本不确定页面中是否已经声明了同名变量
// 那它可以假设还没有声明过
var name = 'Matt';
// 这里没问题,因为可以被作为一个提升声明来处理
// 不需要检查之前是否声明过同名变量
let age = 36;
// 如果age之前声明过,这里会报错
</script>

来实际运行一哈:

在这里插入图片描述

使用try /catch 语句或typeof 操作符也不能解决,因为条件块中 let 声明的作用域仅限于该块。

		<script>
		let name = 'Nicholas';
		let age = 36;
		</script>
		<script>
		// 假设脚本不确定页面中是否已经声明了同名变量
		// 那它可以假设还没有声明过
		if (typeof name === 'undefined') {
		 let name;
		}
		// name被限制在if {} 块的作用域内
		// 因此这个赋值形同全局赋值
		name = 'Matt';
		try{
		// 如果age没有声明过,则会报错
		console.log(age)
		}
		catch(error) {
		let age;
		}
		// age被限制在catch {}块的作用域内
		// 因此这个赋值形同全局赋值
		age = 26;
		</script>

实际运行一哈

在这里插入图片描述

对于let 这个新的ES6声明关键字,不能依赖条件声明模式。

(4)for 循环中的let 声明

在let 出现之前,for 循环定义的迭代变量会渗透到循环体外部:

for (var i = 0; i < 5; ++i) {
			// 循环逻辑
			}
			console.log(i); // 5

实际运行一哈

在这里插入图片描述

改成使用let 之后,这个问题就消失了,因为迭代变量的作用域仅限于for 循环块内部:

// An highlighted block
var foo = 'bar';

实际运行一哈

在这里插入图片描述

经典问题 var for 和 var let问题

来看下面的代码

for (var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 你可能以为会输出0、1、2、3、4
// 实际上会输出5、5、5、5、5

再来看下面的代码

for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 会输出0、1、2、3、4

说下我的理解:同步for循环结束后再执行异步事件。再说明var和let声明后的作用域,var全局作用域始终改变的是同一个i变量,最后访问时得到的是最后i的值。因为let声明后产生独立块级作用域之后,内部访问嵌套外部声明的i变量不会被垃圾回收形成闭包,所以每个i是独立的,最后访问时得到的是预期的结果。(只是我自己的理解,,如有地方不太对请指正)

下面是我做过的一道类似的题

在这里插入图片描述

按照上面我的理解,应该能得出最后的结果是4个4

3.const 声明

const 的行为与let 基本相同,const声明变量时必须同时初始化变量,尝试修改const 声明的变量会导致运行时错误。

const age = 26;
age = 36; // TypeError: 给常量赋值
// const也不允许重复声明
const name = 'Matt';
const name = 'Nicholas'; // SyntaxError
// const声明的作用域也是块
const name = 'Matt';
if (true) {
const name = 'Nicholas';
}
console.log(name); // Matt

如果const 变量引用的是一个对象,那么修改这个对象内部的属性并不违反 const 的限制。

const person = {};
person.name = 'Matt'; // ok

avaScript引擎会为for 循环中的let 声明分别创建独立的变量实例,虽然const 变量跟let 变量很相似,但是不能用const 来声明迭代变量(因为迭代变量会自增):

for (const i = 0; i < 10; ++i) {} // TypeError:给常量赋值

实际运行一下

在这里插入图片描述

如果你只想用const 声明一个不会被修改的for 循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对for-of 和for-in 循环特别有意义

let i = 0;
for (const j = 7; i < 5; ++i) {
console.log(j);
}
// 7, 7, 7, 7, 7
for (const key in {a: 1, b: 2}) {
console.log(key);
}
// a, b
for (const value of [1,2,3,4,5]) {
 console.log(value);
}
// 1, 2, 3, 4, 5

4.声明风格及最佳实践

记住一句话:不使用var,const 优先,let 次之。

书里给的原话是这样的:有了let 和const ,大多数开发者会发现自己不再需要var 了。限制自己只使用let 和const 有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。

使用const 声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用const 来声明变量,只在提前知道未来会有修改时,再使用let 。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。

四、数据类型

ECMAScript有6种简单数据类型,Undefined、Null 、Boolean 、Number 、String 和Symbol

1.typeof 操作符

对一个值使用typeof 操作符会返回下列字符串之一:

  • "undefined" 表示值未定义
  • "boolean" 表示值为布尔值
  • "string" 表示值为字符串
  • "number" 表示值为数值
  • "object" 表示值为对象(而不是函数)或null
  • "function" 表示值为函数
  • "symbol" 表示值为符号

举几个例子

let message = "some string";
console.log(typeof message); // "string"
console.log(typeof(message)); // "string"
console.log(typeof 95); // "number"

实际运行一哈:

在这里插入图片描述

特殊的:typeof null 返回的是"object"

2.Undefined 类型

当使用var或let 声明了变量但没有初始化时,就相当于给变量赋予了undefined 值:

let message;
console.log(message == undefined); // true

来实际运行一哈:

在这里插入图片描述

注意:定义变量未赋值和未定义变量是有区别的

let message; // 这个变量被声明了,只是值为undefined
// 确保没有声明过这个变量
// let age
console.log(message); // "undefined"
console.log(age); // 报错

来实际运行一哈:

在这里插入图片描述

再来看下面的例子:

let message; // 这个变量被声明了,只是值为undefined
// 确保没有声明过这个变量
// let age
console.log(typeof message); // "undefined"
console.log(typeof age); // "undefined"

来实际运行一哈:

在这里插入图片描述

我们发现:对未初始化的变量调用typeof 时,返回的结果是"undefined" ,但对未声明的变量调用它时,返回的结果还是"undefined"

建议:在声明变量的同时进行初始化。这样,当typeof 返回"undefined" 时,你就会知道那是因为给定的变量尚未声明,而不是声明了但未初始化。

undefined 是一个假值

let message; // 这个变量被声明了,只是值为undefined
// age没有声明
if (message) {
// 这个块不会执行
}
if (!message) {
// 这个块会执行
}
if (age) {
// 这里会报错
}

3.Null 类型

let car = null;
console.log(typeof car); // "object"

在这里插入图片描述

console.log(null == undefined); // true

在这里插入图片描述

null是一个假值

let message = null;
let age;
if (message) {
// 这个块不会执行
}
if (!message) {
// 这个块会执行
}
if (age) {
// 这个块不会执行
}
if (!age) {
// 这个块会执行
}

4.Boolean 类型

不同类型与布尔值之间的转换规则

数据类型转换为true的值转换为false的值
Booleantruefalse
String非空字符串“”(空字符串)
Number非零数值(包括无穷值)0、NaN
Object任意对象null
NumberN/A(不存在)undefined

举个例子,了解一下它们的神奇之处

let message = "Hello world!";
if (message) {
console.log("Value is true");
}

运行一哈:

在这里插入图片描述

console.log 会输出字符串"Value is true" ,因为 字符串message 会被自动转换为等价的布尔值true 。

5.Number 类型

最基本的数值字面量格式是十进制整数,直接写出来即可

let intNum = 55; // 整数

整数也可以用八进制(以8为基数)或十六进制(以16为基数)字面量表示。对于八进制字面量,第一个数字必须是零(0),然后是相应的八进制数字(数值0~7)。如果字面量中包含的数字超出了应有的范围,就会忽略前缀的零,后面的数字序列会被当成十进制数,如下所示:

let octalNum1 = 070; // 八进制的56
let octalNum2 = 079; // 无效的八进制值,当成79处理
let octalNum3 = 08; // 无效的八进制值,当成8处理

八进制字面量在严格模式下是无效的,会导致JavaScript引擎抛出语法错误。

ECMAScript 2015或ES6中的八进制值通过前缀0o 来表示;严格模式下,前缀0 会被视为语法错误,如果要表示八进制值,应该使用前缀0o 。

要创建十六进制字面量,必须让真正的数值前缀0x (区分大小写),然后是十六进制数字(0~9以及A~F)。十六进制数字中的字母大小写均可。下面是几个例子:

let hexNum1 = 0xA; // 十六进制10
let hexNum2 = 0x1f; // 十六进制31

注意:由于JavaScript保存数值的方式,实际中可能存在正零 (+0)和负零(-0)。正零和负零在所有情况下都被认为是等同的。

(1)浮点值

要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字。虽然小数点前面不是必须有整数,但推荐加上。下面是几个例子:

let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; // 有效,但不推荐

因为存储浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着0(如1.0),那它也会被转换为整数,如下例所示:

let floatNum1 = 1.; // 小数点后面没有数字,当成整数1处理
let floatNum2 = 10.0; // 小数点后面是零,当成整数10处理

对于非常大或非常小的数值,浮点值可以用科学记数法来表示。科学记数法用于表示一个应该乘以10的给定次幂的数值。ECMAScript中科学记数法的格式要求是一个数值(整数或浮点数)后跟一个大写或小写的字母e,再加上一个要乘的10的多少次幂。比如:

let floatNum = 3.125e7; // 等于31250000

科学记数法也可以用于表示非常小的数值,例如0.000 000 000 000 000 03。这个数值用科学记数法可以表示为3e-17。默认情况下,ECMAScript会将小数点后至少包含6个零的浮点值转换为科学记数法(例如,0.000 000 3会被转换为3e-7)。

浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确。例如,0.1加0.2得到的不是0.3,而是0.300 000 000 000 00004。由于这种微小的舍入错误,导致很难测试特定的浮点值。比如下面的例子:

if (a + b == 0.3) { // 别这么干!
console.log("You got 0.3.");
}

这里检测两个数值之和是否等于0.3。如果两个数值分别是0.05和0.25,或者0.15和0.15,那没问题。但如果是0.1和0.2,如前所述,测试将失败。因此永远不要测试某个特定的浮点值。

(2)值的范围

任何无法表示的负数以-Infinity (负无穷大)表示,任何无法表示的正数以Infinity (正无穷大)表示。

如果计算返回正Infinity 或负Infinity ,则该值将不能再进一步用于任何计算。这是因为Infinity 没有可用于计算的数值表示形式。要确定一个值是不是有限大(即介于JavaScript能表示的最小值和最大值之间),可以使用isFinite() 函数,如下所示:

let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result)); // false
(3)NaN

有一个特殊的数值叫NaN ,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用0除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但在ECMAScript中,0、+0或-0相除会返回NaN :

console.log(0/0); // NaN
console.log(-0/+0); // NaN

运行一哈(真扫盲了,之前不知道)

在这里插入图片描述

如果分子是非0值,分母是有符号0或无符号0,则会返回Infinity或-Infinity :

console.log(5/0); // Infinity
console.log(5/-0); // -Infinity

这之前也不知道,运行一哈,长知识了

在这里插入图片描述

再记一条,NaN和任何数运算结果都是NaN,NaN不等于任何数包括他本身(真邪性)

console.log(NaN == NaN); // false

在这里插入图片描述

为此,ECMAScript提供了isNaN() 函数。该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。把一个值传给isNaN() 后,该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值,如字符串"10" 或布尔值。任何不能转换为数值的值都会导致这个函数返回true 。举例如下:

console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // false,10是数值
console.log(isNaN("10")); // false,可以转换为数值10
console.log(isNaN("blue")); // true,不可以转换为数值
console.log(isNaN(true)); // false,可以转换为数值1

在这里插入图片描述

(4)数值转换

有3个函数可以将非数值转换为数值:Number() 、parseInt() 和parseFloat() 。Number() 是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。对于同样的参数,这3个函数执行的操作也不同。

Number转换规则就不一一列了,直接上例子

let num1 = Number("Hello world!"); // NaN
let num2 = Number(""); // 0
let num3 = Number("000011"); // 11
let num4 = Number(true); // 1

parseInt转换

let num1 = parseInt("1234blue"); // 1234
let num2 = parseInt(""); // NaN
let num3 = parseInt("0xA"); // 10,解释为十六进制整数
let num4 = parseInt(22.5); // 22
let num5 = parseInt("70"); // 70,解释为十进制值
let num6 = parseInt("0xf"); // 15,解释为十六进制整数

再介绍一种高级高级高级用法——转换进制

程序员的情人节

console.log(parseInt(522,16))

有兴趣的小伙伴们可以运行一哈结果,再结合js的其他知识,给喜欢的人一个小惊喜吧

parseFloat转换

let num1 = parseFloat("1234blue"); // 1234,按整数解析
let num2 = parseFloat("0xA"); // 0
let num3 = parseFloat("22.5"); // 22.5
let num4 = parseFloat("22.34.5"); // 22.34
let num5 = parseFloat("0908.5"); // 908.5
let num6 = parseFloat("3.125e7"); // 31250000

6.String 类型

String (字符串)数据类型表示零或多个16位Unicode字符序列。字符串可以使用双引号(")、单引号(')或反引号(`)标示,因此下面的代码都是合法的:

let firstName = "John";
let lastName = 'Jacob';
let lastName = `Jingleheimerschmidt`

跟某些语言中使用不同的引号会改变对字符串的解释方式不同,ECMAScript语法中表示字符串的引号没有区别。不过要注意的是,以某种引号作为字符串开头,必须仍然以该种引号作为字符串结尾。比如,下面的写法会导致语法错误:

let firstName = 'Nicholas"; // 语法错误:开头和结尾的引号必须是同一种

来道曾经做过的考眼力的题

下面哪一个字符串变量定义语句是不正确的
A var mytext="Here is some text!"
B var mytext='Here is some text!'
C var mytext='Here is some text!"
D var mytext="Here is\ some text!"

解析:通过眼力得:选C

(1)转换为字符串

有两种方式把一个值转换为字符串。首先是使用几乎所有值都有的toString() 方法。这个方法唯一的用途就是返回当前值的字符串等价物。比如:

let age = 11;
let ageAsString = age.toString(); // 字符串"11"
let found = true;
let foundAsString = found.toString(); // 字符串"true"

toString() 方法可见于数值、布尔值、对象和字符串值。(没错,字符串值也有toString() 方法,该方法只是简单地返回自身的一个副本。)null 和undefined 值没有toString() 方法。

toString进制转换

默认情况下,toString() 返回数值的十进制字符串表示。而通过传入参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基数的字符串表示,比如:

let num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
console.log(num.toString(8)); // "12"
console.log(num.toString(10)); // "10"
console.log(num.toString(16)); // "a"

在这里插入图片描述

如果你不确定一个值是不是null 或undefined ,可以使用String() 转型函数,它始终会返回表示相应类型值的字符串。

  • 如果值有toString() 方法,则调用该方法(不传参数)并返回结果。
  • 如果值是null ,返回"null" 。
  • 如果值是undefined ,返回"undefined" 。

下面来看几个例子:

let value1 = 10;
let value2 = true;
let value3 = null;
let value4;
console.log(String(value1)); // "10"
console.log(String(value2)); // "true"
console.log(String(value3)); // "null"
console.log(String(value4)); // "undefined"

在这里插入图片描述

(2)模板字面量

ECMAScript 6新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串:

let myMultiLineString = 'first line\nsecond line';
			let myMultiLineTemplateLiteral = `first line
second line`;
			
			console.log(myMultiLineString);
			// first line
			// second line
			console.log(myMultiLineTemplateLiteral);
			// first line
			// second line
			console.log(myMultiLineString === myMultiLineTemplateLiteral); // true

在这里插入图片描述

顾名思义,模板字面量在定义模板时特别有用,比如下面这个HTML模板:

let pageHTML = `
<div>
<a href="#">
<span>Jake</span>
</a>
</div>`;

由于模板字面量会保持反引号内部的空格,因此在使用时要格外注意。格式正确的模板字符串可能会看起来缩进不当:

// 这个模板字面量在换行符之后有25个空格符
let myTemplateLiteral = `first line
second line`;
console.log(myTemplateLiteral.length); // 47
// 这个模板字面量以一个换行符开头
let secondTemplateLiteral = `
first line
second line`;
console.log(secondTemplateLiteral[0] === '\n'); // true
// 这个模板字面量没有意料之外的字符
let thirdTemplateLiteral = `first line
second line`;
console.log(thirdTemplateLiteral);
// first line
// second line

在这里插入图片描述

(3)模板字面量

模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。技术上讲,模板字面量不是字符串,而是一种特殊的JavaScript句法表达式,只不过求值后得到的是字符串。模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域中取值。

字符串插值通过在${} 中使用一个JavaScript表达式实现:

let value = 5;
let exponent = 'second';
// 以前,字符串插值是这样实现的:
let interpolatedString =
value + ' to the ' + exponent + ' power is ' + (value * value);
// 现在,可以用模板字面量这样实现:
let interpolatedTemplateLiteral =
`${ value } to the ${ exponent } power is ${ value * value }`;
console.log(interpolatedString); // 5 to the second power is 25
console.log(interpolatedTemplateLiteral); // 5 to the second power is 

在这里插入图片描述

所有插入的值都会使用toString() 强制转型为字符串,而且任何JavaScript表达式都可以用于插值。嵌套的模板字符串无须转义:

console.log(`Hello, ${ `World` }!`); // Hello, World!

将表达式转换为字符串时会调用toString() :

let foo = { toString: () => 'World' };
console.log(`Hello, ${ foo }!`); // Hello, World!

在插值表达式中可以调用函数和方法:

function capitalize(word) {
return `${ word[0].toUpperCase() }${ word.slice(1) }`;
}
console.log(`${ capitalize('hello') }, ${ capitalize('world') }!`); //

此外,模板也可以插入自己之前的值:

let value = '';
function append() {
value = `${value}abc`
console.log(value);
}
append(); // abc
append(); // abcabc
append(); // abcabcabc

之后的内容大家可以读读了解一下

7. Symbol 类型

Symbol (符号)是ECMAScript 6新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

因为是ES6新加的类型,所以打算看完阮一峰的ES6那本书后再回头补充这块的东西。

8. Object类型

ECMAScript中的对象其实就是一组数据和功能的集合。

let o = new Object();

每个Object 实例都有如下属性和方法。

  • constructor :用于创建当前对象的函数。
  • hasOwnProperty(propertyName ) :用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如o.hasOwnProperty("name") )或符号。
  • isPrototypeOf(object ) :用于判断当前对象是否为另一个对象的原型。
  • propertyIsEnumerable(propertyName ) :用于判断给定的属性是否可以使用(本章稍后讨论的)for-in 语句枚举。与hasOwnProperty() 一样,属性名必须是字符串。
  • toLocaleString() :返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
  • toString() :返回对象的字符串表示。
  • valueOf() :返回对象对应的字符串、数值或布尔值表示。通常与toString() 的返回值相同。

五、操作符

在应用给对象时,操作符通常会调用valueOf()和/ 或toString() 方法来取得可以计算的值。

1.一元操作符

只操作一个值的操作符叫一元操作符 (unary operator)。

(1)递增(++)和递减(–)

最基本的自加自减输出结果之类的很简单,就不详细说了,主要说一下后面的规则

递增和递减操作符遵循如下规则。

  • 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值。
  • 对于字符串,如果不是有效的数值形式,则将变量的值设置为NaN 。变量类型从字符串变成数值。
  • 对于布尔值,如果是false(true) ,则转换为0(1)再应用改变。变量类型从布尔值变成数值。
  • 对于浮点值,加1或减1。
  • 如果是对象,则调用其valueOf() 方法取得可以操作的值。对得到的值应用上述规则。如果是NaN,则调用toString() 并再次应用其他规则。变量类型从对象变成数值。

上例子:

let s1 = "2";
let s2 = "z";
let b = false;
let f = 1.1;
let o = {
valueOf() {
return -1;
}
};
s1++; // 值变成数值3
s2++; // 值变成NaN
b++; // 值变成数值1
f--; // 值变成0.10000000000000009(因为浮点数不精确)
o--; // 值变成-2

对照规则,自己领悟

(2)一元加和减

直接上例子

let num = 25;
num = +num;
console.log(num); // 25

如果将一元加应用到非数值,则会执行与使用Number() 转型函数一样的类型转换:布尔值false 和true 转换为0和1,字符串根据特殊规则进行解析,对象会调用它们的valueOf() 和/或toString() 方法以得到可以转换的值。

let s1 = "01";
let s2 = "1.1";
let s3 = "z";
let b = false;
let f = 1.1;
let o = {
valueOf() {
return -1;
}
};
s1 = +s1; // 值变成数值1
s2 = +s2; // 值变成数值1.1
s3 = +s3; // 值变成NaN
b = +b; // 值变成数值0
f = +f; // 不变,还是1.1
o = +o; // 值变成数值-1

一元减由一个减号(- )表示,放在变量前头,主要用于把数值变成负值,如把1转换为-1。示例如下:

let num = 25;
num = -num;
console.log(num); // -25

对数值使用一元减会将其变成相应的负值(如上面的例子所示)。在应用到非数值时,一元减会遵循与一元加同样的规则,先对它们进行转换,然后再取负值:

let s1 = "01";
let s2 = "1.1";
let s3 = "z";
let b = false;
let f = 1.1;
let o = {
valueOf() {
return -1;
}
};
s1 = -s1; // 值变成数值-1
s2 = -s2; // 值变成数值-1.1
s3 = -s3; // 值变成NaN
b = -b; // 值变成数值0
f = -f; // 变成-1.1
o = -o; // 值变成数值1

2.位运算符

相信小伙伴们这里的基础非常好就不一一列举了

3.逻辑运算符(与或非)

与或非也不多提,记住一下什么时候返回true和false就可

非(!)有一个小技巧:

想要取得反向布尔值可以加一个!
想要正向转换布尔值可以加双!

4.乘性运算符(乘法,除法,取模)

整体不用多说,注意一下这里的除法不是除完了取整,而是有小数

如果想要除完了取整可以加parseInt获取

还有一个是分隔字符串的技巧

我们看一个例子:定义一个八位数字字符串表示时间,把年,月,日分割出来

let str = "20160511";
let month = parseInt(str % 1e+4 / 100)
let year  = parseInt(str / 1e+4);
let date  = str % 100 ;
console.log(year,month,date)

总结一下就是从前往后割就用/10的几次方,从后往前割是%10的几次方,两个配合这parseInt使用可以割出来我们想要的东西

我们再看下面的例子:

let b = 3*1.2
console.log(b) // 3.5999999999999996

实际运行一哈

在这里插入图片描述

我们期待的结果是3.6,但是它实际上是3.5999999999999996。

我们可以巧用先乘后除的思想,3*12/10

let b = 3*12/10
console.log(b) // 3.6

在这里插入图片描述

我们得到了预期的效果

4.指数运算符

这里不多说,直接上例子:用三种方法写出a的三次方

a*a*a
Math.pow(a,3)
a**3

5.加法操作符

(1)字符串拼接

用我的话说就是,如果+两边不同时为Number类型,会拼接成一个字符串

举个例子:

let a = "10";
let b = "20";
console.log(a+b) // 1020

实际运行一哈

在这里插入图片描述

或者是把其中一个字符串的引号去掉变为数字类型,结果是同样的,就不再打印一遍了

(2)两值相加

+两边同时为数字类型的时候,他们的值是可以想加的,还是用上面的例子

let a = 10;
let b = 20;
console.log(a+b) // 30

在这里插入图片描述

6.关系操作符

基础的一些东西就不在叙述,注意一下字符串的比较大小问题

我们看个例子:

let result = "Brick" < "alphabet"; // true

实际运行一哈:

在这里插入图片描述

按理说B应该比a大呀,这是咋回事呢?实际上字符串比较的是首位的ascll编码,字母B的编码是66,字母a的编码是97。想要得到预期结果,可以把他们变成全大写和全小写。

let result = "Brick".toLowerCase() < "alphabet".toLowerCase(); // false

7.相等操作符

基础的东西不再叙述,讲一下近似等于和绝对等于

(1)近似等于(双等号)

近似等于在进行比较时,把两边转换为相同类型后进行比较

(2)绝对等于(三等号)

绝对等于在进行比较时,优先比较两边的数据类型

相信看过面经的小伙伴都会对这里的一类题型搞得脑壳疼,我总结了一下:在 js 中,只有 ''、0、null、undefined、NaN,这些是 false,其余都是 true

再记住几个特殊的

console.log( null == 0); // false
console.log( null == false); // false
console.log( false == 0); // true
console.log( "" == 0); // true

拿下这类题问题不大。

7.条件操作符(三目运算)

三目运算相信大家都会了,就不再过多叙述

六、语句

最基础的语句就不再提了,说一说后面用的。

1.for-in语句

for-in 语句是一种严格的迭代语句,用于枚举对象中的非符号键属性,语法如下:

for (property in expression) statement

下面是一个例子:

for (const propName in window) {
document.write(propName);
}

来运行一哈

在这里插入图片描述

这个例子使用for-in 循环显示了BOM对象window 的所有属性。每次执行循环,都会给变量propName 赋予一个window 对象的属性作为值,直到window 的所有属性都被枚举一遍。与for 循环一样,这里控制语句中的const 也不是必需的。但为了确保这个局部变量不被修改,推荐使用const 。

ECMAScript中对象的属性是无序的,因此for-in 语句不能保证返回对象属性的顺序。换句话说,所有可枚举的属性都会返回一次,但返回的顺序可能会因浏览器而异。

如果for-in 循环要迭代的变量是null 或undefined ,则不执行循环体。

2.for-of语句

for-of 语句是一种严格的迭代语句,用于遍历可迭代对象的元素,语法如下:

for (property of expression) statement

下面是示例:

for (const el of [2,4,6,8]) {
document.write(el);
}

在这个例子中,我们使用for-of 语句显示了一个包含4个元素的数组中的所有元素。循环会一直持续到将所有元素都迭代完。与for 循环一样,这里控制语句中的const 也不是必需的。但为了确保这个局部变量不被修改,推荐使用const 。

for-of 循环会按照可迭代对象的next() 方法产生值的顺序迭代元素。如果尝试迭代的变量不支持迭代,则for-of 语句会抛出错误。

3.with语句

with 语句的用途是将代码作用域设置为特定的对象,其语法是:

with (expression) statement;

使用with 语句的主要场景是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供便利,如下面的例子所示:

let qs = location.search.substring(1);
let hostName = location.hostname;
let url = location.href;

上面代码中的每一行都用到了location 对象。如果使用with 语句,就可以少写一些代码:

with(location) {
let qs = search.substring(1);
let hostName = hostname;
let url = href;
}

这里,with 语句用于连接location 对象。这意味着在这个语句内部,每个变量首先会被认为是一个局部变量。如果没有找到该局部变量,则会搜索location 对象,看它是否有一个同名的属性。如果有,则该变量会被求值为location 对象的属性。

严格模式不允许使用with 语句,否则会抛出错误。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PCIE(Peripheral Component Interconnect Express)是一种计算机扩展总线标准,用于连接外部设备与计算机主板之间的数据传输。PCIe连接的设备可以是显卡、声卡、网卡等。 Flow control(流量控制)是PCIE的一种基础机制,用于在设备之间传输数据时确保数据的可靠传输。 PCIE的flow control可以分为两种方式:Credit-based Flow Control(基于信用控制)和Acknowledgment/Negative Acknowledgment Flow Control(确认/否定确认控制)。 基于信用控制是PCIE最常用的流量控制机制。发送方设备在发送数据之前会向接收方设备发送一个信用(credit)值,表示发送方设备可以发送的最大数据量。接收方设备在接收到数据后会发回一个更新的信用值给发送方设备,发送方设备根据接收到的信用值确定下一次可以发送的数据量。通过这种方式,可以有效控制不同速度的设备之间的数据传输,避免数据丢失或信道阻塞。 确认/否定确认控制是PCIE的一种备用方式,当发送方设备发送数据后,接收方设备会发回一个确认或否定确认信号给发送方设备,以告知是否成功接收数据。如果发送方设备收到否定确认信号,则会重新发送数据,确保数据的可靠性。 总结来说,PCIE的flow control机制是为了确保数据的可靠传输而设计的。基于信用控制和确认/否定确认控制是两种常用的流量控制方式,可以根据不同的需求选择适合的方式来控制数据的传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值