三 数据类型

教程

https://wangdoc.com/javascript/types/general.html

1 概述

1.1 数据类型分类

  • 数值,number, 整数和小数
  • 字符串,string, 文本 Hello World
  • 布尔值,bool
  • undefined, 表示未定义或不存在,比如仅是声明的变量它的值就是 undefined
  • null,表示空值,此处的值为空
  • 对象,object,各种值组成的集合

number , string & bool 合成为原始类型(primitive type),因为它们是最基本的数据类型,不能再细分了。
对象是合成类型(complex type),因为一个对象往往由多个原始类型的值合成,是一个存放各种值的容器。
undefined 和 nulll,一般是将它们看为两个特殊值。

对象是最复杂的数据类型,又可以分为三个子类型:

  • 狭义的对象(object)
  • 数组 (array)
  • 函数 (function)

狭义的对象和数组是两种不同的数据组合方式,教程中的对象都是指的是狭义的对象。

函数其实是处理数据的方法,JavaScript 把它当做一种数据类型,可以赋值给变量,这给编程带来灵活性,也为 JS 的函数式编程 奠定基础。Python 也可以把函数赋值给变量。Go 也是支持把函数赋值给变量的。

1.2 typeof 运算符

JavaScript 有三种方法,确定一个值到底是什么类型

  • typeof 运算符
  • instanceof 运算符
  • Object.prototype.toString 方法

typeof 可以返回一个值的数据类型。(这个 typeof 居然不是驼峰命名,也是一大怪)
数值、字符串、布尔值分别返回 "number" / "string" / "boolean"

typeof 123  //  "number"
typeof "abc" // "string"
typeof false // "boolean"

函数返回 "function"

function f() {}
typeof f // "function"

undefined 返回 "undefined"

typeof undefined // "undefined"
typeof 的使用场景

可以用来检测一个没有声明的变量

// 上下文中没有对 v 的任何定义
// 如果直接调用 v,会:Uncaught ReferenceError: v is not defined
typeof v  // "undefined" 

实际编程中,这个特点通常放在判断语句中。

// 错误的写法
if (v){
....
}
// ReferenceError: v is not defined

// 正确的写法
if (typeof v  === "undefined"){
  ....
}

对象返回 "object"

typeof window / document  //  "object"
typeof {} // "object"
typeof [] // "object"

数组的类型也是 “object”,这表示在 JS 内部,数组本质上是一种特殊的对象。
null 返回 "object"

typeof null
// "object"

null 的类型是 "object" 这是历史原因造成的。JS 语言第一版只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),当时只是把 null 当成 object 的一种特殊值。后来 null 独立出来,作为一种单独的数据类型,但是为了兼容以前的代码,typeof null 返回 "object" 就没有办法改了。

2 null, undefined 和 布尔值

2.1 undefined 和 null 的区别

教程 http://www.ruanyifeng.com/blog/2014/03/undefined-vs-null.html

  • 相似性
    将一个变量赋值为 undefined 或 null,几乎没有区别:
let a = undefined;
let b =  null;

使用相等运算符,会直接报告它们相等

undefined == null // true

它们的布尔值都是 false

Boolean(null) // false
Boolean(undefined) // false
  • 差别
    null 表示一个空对象,在转为数值时可以转化为 0; undefined 是一个“此处无定义”的原始值,转化为 NaN
Number(null) // 0
5 + null // 5
Number(undefined)  // NaN
  • undefined 表示未定义,下面是 undefined 的典型场景
  1. 变量声明了,但是没有赋值
var i;
i // undefined
  1. 调用函数时,应该提供的参数没有提供,该参数等于 undefined; 这一点算得上是拙劣的设计了,如果是其他的语言会直接抛出异常。
function f(x){
  return x;
}
f() // undefined
  1. 对象没有赋值的属性
var o = new Object();
o.p // undefined
  1. 函数没有返回值,默认返回 undefined
function f() {}
f() // undefined

2.1 布尔值

Python 和 JS 都会对一些值进行隐式的布尔值转换。Go 和 Java 这种强类型语言则不会

  • undefined
  • null
  • false
  • 0
  • NaN
  • "" 或 ‘’(空字符串)
    这几种值的布尔值都是 undefined。在应用中,可以写出这种形式:
if (''){
}

注意,空 [] 和 {} 对应的布尔值是 true, 这也是 JS 的特性:

Boolean([]) // true
Boolean({}) // true

3 数值

https://wangdoc.com/javascript/types/number.html

3.1 概述

3.1.1 整数和浮点数

JavaScript 内部,所有数字都是以 64 位浮点数形式存储,即使整数也是如此。所以,在 1 与 1.0 是相同的,是同一个数。

1 === 1.0 // true

这就是说,JavaScript 语言的底层根本没有整数,所有数字都是小数(64 位浮点数)。容易造成混淆的是,某些运算是只有整数才能完成,此时 JavaScript 会自动把 64 位浮点数转为 32 位整数,然后再进行运算。

由于浮点数不是精确的值,所以涉及到小数的比较和运算要特别小心。

0.1 + 0.2 
// 0.300000000000000004
0.3 / 0.1
// 2.999999999999999996
0.3 - 0.2
// 0.099999999999999998

3.1.2 数值精度

精度最多只能到 53 个二进制,这意味着,绝对值小于等于 2 的 53 次方的整数。

Math.pow(2, 53)
// 9007199254740992

3.1.3 数值范围

如果一个数大于等于 2 的 1024 次方,那么就会发生 “正向溢出”,即返回 JavaScript 无法表示这么大的数,这时就会返回 Infinity.

Math.pow(2, 1024) // Infinity

如果一个数小于等于 2 的 -1075 次方,那么就会发生 "负向溢出",JS 无法表示这么小的数,会直接返回0

Math.pow(2, -1075) // 0

JavaScript 提供 Number 对象的 MAX_VALUE 和 MIN_VALUE 属性,返回可以表示的具体的最大值和最小值

Number.MAX_VALUE  // 1.7....
Number.MIN_VALUE // 5e-324

3.2 数值的表示法

JavaScript 的数值有多种表示方法,可以用字面形式直接表示,比如 35 (十进制)和 0xFF (十六进制)

123e3 // 123000
123e-3 // 0.123
-3.1E+12
.1e-23

科学计数法允许字母 e 和 E 的后面,跟着一个整数,表示这个数值的指数部分。

以下两种情况,JavaScript 会自动将数值转为科学计数法表示,其他情况都采用字面形式表示。
1)小数点前的数字多于 21 位。

1111111111111111111111111111111111111111111111111
// 1.1111111111111112e+48
  1. 小数点后面的零多于5个。
// 小数点后紧跟 5 个以上的零,
// 就自动转化为科学计数法
0.0000003 // 3e-7

3.3 数值的进制

使用字面量(literal)直接表示一个数值时,JavaScript 对整数提供四种进制的表示方法:十进制、十六进制、八进制、二进制。

  • 十进制:没有前导 0 的数值。
  • 八进制:有前缀 0o00的数值,或者有前导0、且只用到 0-7 的八个阿拉伯数值。
  • 十六进制:有前缀 0x0X的数值
  • 二进制:有前缀 0b0B 的数值。

3.4 特殊数值

JavaScript 提供了几个特殊的数值。

3.4.1 正零和负零

JavaScript 的 64 位浮点数之中,有一个二进制位是符号位。这意味着,任何一个数都有一个对应的负值,就连 0 也不例外。

JavaScript 中存在两个0:一个是+0,一个是-0,区别就是64位浮点数表示法的符号位不同。它们是等价的。

-0 === +0 //true
0 === -0 //true
0 === +0 //true

几乎在所有场合,正零和负零都会被当作正常的 0

+0 // 0
-0 // 0
(-0).toString() // '0'
(+0).toString() // '0'

唯一有区别的场合是,+0 或 -0 当作分母,返回的值是不相等的。

(1 / +0) === (1 / -0) // false

上面的代码之所以出现这样的结果,是因为除以正零得到 +Infinity,除以负零得到-Infinity,这两者是不相等的。

3.4.2 NaN

(1) 含义
NaN 是 JS 的特殊值,表示 “非数字”(Not a Number), 主要出现在将字符串解析成数字出错的场合。

5 - 'x' // NaN

上面的代码运行时,会自动将字符串 x 转化为数值,但是由于 x 不是数值,所以最终得到的结果是 NaN,表示它是非数字.

NaN 不是特殊的数据类型,而是一个特殊数值,它的数据类型依然属于 Number,使用 typeof 运算符可以看清楚。

typeof NaN // 'number'

(2) 运算规则
NaN 不等于任何值,包括它本身。

NaN === NaN // false

数组的 indexOf 方法内部使用的是严格相等运算符,所以该方法对 NaN不成立。

[NaN].indexOf(NaN)  // false

NaN 在的布尔值是 false

Boolean(NaN) // false

NaN 与任何数(包括它自己)的运算,得到的结果都是 NaN.

NaN + 32 // NaN
NaN + NaN // NaN

3.4.2 Infinity

(1) 含义
Infinity 表示“无穷”,用来表示两种场景。一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非 0 数值除以 0,得到 Infinity

// 场景一
Math.pow(2, 1024) // Infinity
场景二
1/ 0 // Infinity

Infinity 有正负之分,Infinity 表示正的无穷,-Infinity 表示负的无穷。

Infinity === -Infinity // false
1 / -0 // -Infinity

由于数值正向溢出和负向溢出和被0除,JavaScript 都不报错,而是返回 Infinity, 所以单纯的数学运算机会没有可能抛出错误。

Infinity大于一切数值(除了 NaN), -Infinity 小于一切数值(除了 NaN

Infinity > 111111111111111111111111 //true
-Infinity < -11111111111111 // true

InfinityNaN 比较,总是返回 false

Infinity < NaN // false
Infinity > NaN  // false

(2) 运算规则
Infinity 的运算规则,符合无穷的数学计算规则。

5 * Infinity // Infinity
5 - Infinity // -Infinity
Infinity / 5 // Infinity
5 / Infinity // 0

0 乘以 Infinity,返回 NaN; 0 除以 Infinity, 返回 0; Infinity 除以0,返回 Infinity。

0 * Infinity // NaN
0 / Infinity // 0
Infinity / 0 // Infinity

Infinity 加上或乘以 Infinity, 返回的还是 Infinity。

Infinity + Infinity // Infinity
Infinity * Infinity // Infinity

Infinity 减去或除以 Infinity, 得到 NaN

Infinity - Infinity // NaN
Infinity / Infinity // NaN

Infinity 与 null 计算时, null 会转成0,等同于与 0 的计算。

null * Infinity // NaN
null / Infinity // 0
Infinity / null // Infinity

Infinity 与 undefined 计算,返回的都是 NaN

undefined + Infinity // NaN
undefined - Infinity // NaN
undefined * Infinity // NaN
undefined / Infinity // NaN
Infinity / undefined // NaN

3.5 与数值相关的全局方法

全局方法,也就是像 Python 那样的内置函数

3.5.1 parseInt()

(1) 基本用法
parseInt 方法用于将字符串转为整数

parseInt('123')   // 123

如果字符串头部或尾部有空格,空格会被自动去除

parseInt("          123    ") // 123

如果 parseInt 的参数不是字符串,则会先转为字符串再转化。

parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1

字符串转化为整数的时候,是一个个字符依次转化下去的,如果遇到不能转化为数字的字符,就不再进行下去,返回已经转好的部分。——这个特性真是中二!

parseInt("12e3") // 12

如果字符串的第一个字符就不能转化为数字的话,则会返回NaN。(第一位为数字的正负号除外)

parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') NaN

如果字符串以 0x0X 开头,parseInt 会将其按照十六进制数解析。

parseInt('0x10')  // 16

如果字符串以 0 开头,将其按照 10 进制解析。

parseInt('011') // 11

对于那些会自动转为科学计数法的数字, parseInt 会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。

parseInt(111111111111111111111111111111) // 1
// 等同于
parseInt("1.11111111112e+54") // 1
parseInt(0.000008) // 8
parseInt('8e-7') //8

(2) 进制转换
parseInt 方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。默认情况下,parseInt 的第二个参数为10,表示默认是十进制转化为十进制。

parseInt('1000') // 1000
// 等同于
parseInt('1000', 10) // 1000

下面是转化指定进制的数的例子

parseInt('1000', 2) // 2
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512

如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回 NaN 。如果第二个参数是0、undefined 和 null,则直接忽略

parseInt('10', 37) // NaN
parseInt('1000', 2.2) // 2

3.5.2 parseFloat()

parseFloat() 也是一个内置方法,作用是跟 parseInt 一样的。

3.5.3 isNaN()

isNaN 方法可以用来一个值是否为 NaN

isNaN(NaN)  // true
isNaN(123) // false

但是,isNaN 只对数值有效,如果传入其他值,会被先转成数值。比如,传入字符串的时候,字符串会被先转成 NaN,所以最后返回 true,这一点要特别引起注意。也就是说,isNaNtrue 的值,有可能不是 NaN,而是一个字符串。

isNaN('Hello') // true
isNaN(Number('Hello')) // true

出于同样的原因,对于对象和数组,isNaN 也返回 true

isNaN({}) // true
// 等同于
isNaN(Number({})) // true
// ...
isNaN(['xzy']) // true
// 等同于
isNaN(Number(['xzy'])) //true

但是,对于空数组和只有一个数值成员的数组,isNaN 返回 false。

isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false

上面代码之所以返回 false,原因是这些数组能被 Number 函数转成为数值,请参见《数据类型转换》一章。
因此,使用isNaN之前,最好判断一下数据类型。

function myIsNaN(value){
  return typeof value === 'number' && isNaN(value);
}

myIsNaN(NaN) // true

判断 NaN 更可靠的方法是,利用 NaN 为唯一不等于自身的值的这个特点,进行判断:

function myIsNaN(value){
  return value !== value;
}

3.5.4 isFinite()

isFinite 方法返回一个布尔值,表示某个值是否正常的数值。

isFinite(Infinity) // false
isFinite(-Infinity) // false
isFinite(NaN) // false
isFinite(undefined) // false
isFinite(null) // true
isFinite(-1) // true

除了 Infinity / -Infinity / NaNundefined 这个值会返回 false,isFinite 对于其他的数值都会返回 true

4 字符串

4.1 概述

4.1.1 定义

字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。

'abc'
"abc"

在 Java 、Go 等静态语言中,有字符这个概念,用单引号引起来,字符串是字符数组的语法糖用双引号引起来。

可以用反斜杠来转义。

"Did she say \"Hello\" ?"

由于 HTML 语言的属性值使用双引号,所以很多项目约定 JavaScript 语言的字符串只使用单引号。

let longStr = `long \ long`

可以使用 \ 多行定义字符串,在 es6 中可以使用 `` 来定义多行字符串。
也可以使用 + 连接多个单行字符串。
还有一种利用多行注释的变通方法。

(function(){/*
line 1
line 2
line 3
*/}).toString().split('\n').slice(1, -1).join('\n')

4.1.2 转义

反斜杠 () 在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符。
需要用反斜杠转义的特殊字符,主要有下面这些:

  • \0: null
  • \b: 后退键
  • \f: 换页符
  • \n: 换行符
  • \r: 回车键
  • \t: 制表符
  • \ : 反斜杠
  • ' : 单引号
  • " : 双引号

反斜杠还有三种特殊用法。

  1. \HHH
    反斜杠后面紧跟三个八进制数(000377),代表一个字符。HHH 对应该字符的 Unicode 码点,比如 \251 表示版权符号。显然,这种方法只能输出 256 种字符。
  2. \xHH
    \x 后面紧跟两个十六进制数(00FF),代表一个字符。HH 对应该字符的 Unicode 码点,比如 \xA9 表示版权符号。
  3. \uXXXX
    \u 后面紧跟四个十六进制数(0000FFFF),代表一个字符。XXXX 对应该字符的 Unicode 码点,比如 \u00A9 表示版权符号。

如果在非特殊字符前面使用反斜杠,则反斜杠会被省略。

'\a' // "a"

如果字符串的正常之中,需要包含反斜杠,用来对自身转义。

"Prev \\ Next"
// "Prev \ Next"

4.1.3 字符串与数组

字符串可以视为字符数组,可以进行索引。(阮大说的真费劲)
如果方括号中不是数字,或者索引越界,则会返回 undefined, 而不是强类型语言一样报错。

let s = 'Hello';
s[0] // "h"
s["0"] // "h"
s[null] // undefined
s[1000] // undefined

字符串只是和数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。
对字符串进行删改的操作会失效,但是不会出错。

let s = "hello";
delete s[0];
s[1] = 'a';
s // hello

4.1.4 length 属性

length 属性返回字符串的长度,该属性也是无法改变的

let s = "hello";
s.length // 5
s.length = 10
s.length // 5

4.2 字符集

JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode 表示。

JavaScript 不仅以 Unicode 储存字符,还允许直接在程序中使用 Unicode 码点表示字符,即将字符写成 \uxxxx 的形式,其中xxxx代表该字符的 Unicode 码点。比如,\u0049 代表版权符号。

let s = '\u00A9';
s // "©"

解析代码的时候,JavaScript 会自动识别一个字符是字面形式表示,还是 Unicode 形式。输出给用户的时候,所以字符都会转成字面形式。

let f\u006F\u006F = 'abc';
foo // "abc"

上面代码中,第一行的变量名是 foo 是 Unicode 形式表示,第二行是字面形式表示。JavaScript 会自动识别。

每个字符在 JavaScript 内部以 16 位(2个字节)的 UTF-16 格式存储。也就是说,JavaScript 的单位字符长度固定为 16 位长度,2个字节。

UTF-16 有两种长度:对于码点在 U+0000U+FFFF之间的字符,长度为16位(2个字节);对于码点在 U+10000U+10FFFF之间的字符,长度为 32 位(即 4 个字节)。

JavaScript 对 UTF-16 的支持是不完整的,由于历史原因,只支持两字节的字符,不支持四字节的字符。所以对于四字节字符,浏览器会正确识别这是一个字符,但是 JS 会认为这个两个字符。

总之,JS 返回的字符串长度可能是不正确的。

4.3 Base64 转码

有时候,文本里面包含一些不可打印的符号,比如 ASCII 码 0 到 31 的符号都无法打印出来,这时可以使用 Base64 编码,将它们转成可以打印的字符。另外一个场景是,有时需要以文本格式传递二进制数据,可以使用 Base64 编码。

Base64 就是一种编码方式,可以将任意值转化成 0~9 \ A-Z \ a-z + 和 / 这64个字符组合的可打印字符。使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理。

JavaScript 原生提供了两个 Base64 相关的方法。

  • btoa(): 任意值转为 Base64 编码。
  • atob(): Base64 编码转为原来的值。
let str = "Hello";
btoa(str) // SGVsbG8=
atob("SGVsbG8=") // Hello

注意,这两个方法不适合非 ASCII 码的字符,会报错。

btoa('你好') // 报错,The string to be encoded contains characters outsie of the Latin1 range (字符串包含了非 Latin1(拉丁) 字符)

要将非 ASCII 码字符转 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。

function b64Encode(str){
  return btoa(encodeURIComponent(str));
}

function b64Decode(str){
  return decodeURIComponent(atob(str));
}

4.4 ASCII 与 非ASCII 转化

// 转化为 ASCII
encodeURIComponent(str)
// 将 ASCII 转化为 非 ASCII
decodeURIComponent(str)

5 对象

https://wangdoc.com/javascript/types/object.html

5.1 概述

5.1.1 生成方法

对象( Object ) 是 JavaScript 语言的核心,也是最重要的数据类型。
简单来说,对象就是一组键值对的“集合”

5.1.2 键名

键名是数值时,会自动转化为字符串。但是打印出来的时候看不出来。

let d = {1:"a"}

如果键名不符合变量名的命名规则,如第1位是数值或者以空格开头,那么需要用引号引起来。

d["1px"] = "c"

对象的每一个键名都又称为“属性”(property),键值可以是任何数据类型。如果一个属性的值是函数时,这个属性一般被称作方法,它可以像函数一样被调用。

let b = {
  f : (x)=>{return x}
}
b.f(3) // 3

如果属性的值是一个对象,就形成了链式引用。

let a = {b:{c:3}}
a.b.c //3

5.1.3 对象的引用

如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是指向同一个内存地址。如果修改其中一个变量,会影响到其他所有的变量。

let a = {w:1}
let b = a;
b.w = 2
a.w // 2

这个问题很经典,python 里面的深拷贝和浅拷贝也是这个样子,Java 和 Go 也有类似的问题。这个问题的关键是,要分清这个值是什么类型,如果值是原始类型/基础类型等,那么分别赋予不同的变量名就会发生值拷贝,修改彼此不发生影响。如果是值引用类型,这个类型通常是个容器,里面的成员是个指向真实值地址的引用。这时修改这个容器,就会影响所有的变量名。

5.1.4 表达式还是语句

{foo:123}

如果行首出现这行代码,会有两种含义:
这是一个对象,
这是一个语句,表示是一个代码块,里面一个标签 foo ,指向代码123。
所以为了避免歧义,行首是大括号时,最好在前面加上括号。

5.2 属性的操作

5.2.1 属性的读取

有两种方式,用点和方括号(python就是这样)。
但是需要注意,当使用方括号时,键必须被引号引起来,否则就会被当作变量处理。

let a = "ok"
let d = {
  a : 1,
  ok: 2
}
d[a] // 2
d["a"] // 1

键名是数字可以不加引号,因为可以自动转化为字符串。并且数字键名不能使用点运算符,因为会被当作小数点。

let a  = {3:"three"}
a.3 // SyntaxError: Unexpected number
a[3] // "three"

5.2.2 属性的赋值

跟 python 一样,略。

5.2.3 属性的查看

查看所有属性:Object.keys()

let w = {1:2,2:3,3:4}
Object.keys(w) // ["1","2","3"]

python 跟这个不一样,

w = {1:2,2:3,3:4}
w.keys()

造成这样不一样的原因是,在 python 中讲究一切皆对象。代码中的 w 是 python dict 类的一个实例。而 python 定义的dict有一个 keys() 的方法。

而JS中并不是这样做的。而是把 w 当作参数传入进去。

5.2.4 属性的删除:delete 命令

删除属性使用 delete,但是 delete 在删除属性时无论该属性是否存在都会返回 true。

let obj = {}
delete obj.p // true

除非是删除私有属性才会返回 false,并且这样的私有属性不会通过 delete 删除。

let obj = Object.defineProperty({},'p',{value:2})
obj.p // 2
delete obj.p // false

另外需要注意,继承的属性是不能删除的

let obj = {}
delete obj.toString // true

阮大这个思考很深入啊,之前没有想过删除一下从内置的继承属性

del w.keys
// AttributeError: 'dict' object attribute 'keys' is read-only
// keys 只是可读属性。属性分两种,可读和可修改

5.2.5 属性是否存在:in 运算符

基本上可以按照 Python 中的 in 来理解,可以用在对象和数组上。Python 中的 in 作用的对象要求必须是 iterable 。

in 运算符有一个问题,它不能识别哪些属性是对象自身的,哪些属性是继承的。

let arr = []
"toString" in arr // true

这时,可以使用对象的 hasOwnProperty 方法判断一下,是否为对象自身的属性。

let obj = {};
if('toString' in obj){
  console.log(obj.hasOwnProperty('toString'));
}

5.2.6 属性的遍历:for ... in 循环

for ... in 循环用来遍历一个对象的所有属性。

for(let i in {a:1,b:2}){
  console.log(i)
}

数组本质上也是一种特殊的属性,所以用 for ... in 遍历返回的是索引位置。
for ... in 循环使用时有两个注意点:

  • 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性
  • 它不仅遍历对象自身的属性,还遍历继承的属性。
    如果只想遍历对象自身的属性,需要结合 hasOwnProperty, 在循环内部判断一下,某个属性是否为对象自身的属性
let person = {name:333}
for(let i in person){
  if (person.hasOwnProperty(i)){
    console.log(i);
  }
}

使用 for ... of 遍历对象会抛异常:

typeError : intermediate value is not iterable 
复制值是不可迭代的

这个错误也凸显 for ... of 和 for ... in 真正的区别:
for ... in 是遍历对象;
for ... of 是遍历可迭代对象,这个才是模仿 python 的 for ... in...

5.3 with 语句

with 语句的格式如下:

with (对象){
  语句;
}

它的作用是操作同一个对象的多个属性时,提供一些书写方便。

let obj = {
  p1 : 1,
  p2 : 2,
}

with (obj){
 p1 = 4;
 p2 = 5;  
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;

注意,如果 with 区块内部有变量的赋值操作,必须是当前已经存在的属性,否则就会创造一个当前作用域的全局变量。

let obj = {a:1}
with (obj){
  a = 8;
  b = 99;
}
obj.a // 8
obj.b // undefined
b // 99

with 区块没有改变作用域,它的内部依然是当前作用域。这造成了 with 语句的一个很大弊病,就是绑定对象不明确。
编译器无法在编译时提前优化,因为 它无法判断with 语句中的变量是全局变量还是 obj 的一个属性,只能等到运行时判断,这就拖慢了运行速度。

python 中也有 with,但是 with 是一个上下文管理工具,和 js 中 with 作用完全不一样。

6 函数

教程:https://wangdoc.com/javascript/types/function.html

6.1 概述

6.1.1 函数的声明

JavaScript 有三种声明函数的方法。
(1) 使用 function
(2) 函数表达式

let a = ()=>{}

一般这种形式,函数表达式都是一个匿名函数,但是也可以是一个带有函数名的函数。这时,该函数名只在函数体内部有效,在函数体外部无效。

let print = function x(){
  console.log(typeof x);
}
x // ReferenceError: x is not defined
print() // function 

这种写法有两种好处:
一是可以在函数体内部调用自身,
二是方便排错,除错工具显示函数调用栈时,将显示函数名,而不再这里是一个匿名函数。

let f = function f() {};

需要注意的是,函数的表达式需要在语句的结尾加上分号,表示语句结束。函数的声明在结尾的大括号后面不用加分号。
第三种声明函数的方式是 Function 构造函数

let add = new Function(
'x',
'y',
'return x + y'
)
// 这种写法等同于
function add(x,y){
  return x + y
}

可以传递任意数量的参数给 Function 构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。

6.1.2 函数的重复声明

如果一个函数被多次重复声明,那么后面的声明就会覆盖前面的声明。
阮大真是脑回路清奇,实验了一下,在 Python 也是如此,后面的声明可以覆盖前面的声明。
但是,由于 JS 函数名的提升(参见下文), 前一次声明在任何时候都是无效的,这一点特别注意。Python 中因为不会像JS 这样先解释在运行,而是边解释边运行,所以还是有效的。

function a(){
  console.log("111");
}
a()
function a(){
  console.log("222");
}
a()

// 222
// 222

6.1.3 圆括号运算符、return 语句和递归

圆括号运算符就是调用函数。
递归就是递归计算呗

6.1.4 第一等公民

函数是 JS 中的一等公民,也就是说函数是一种值,它与其他值(数值、布尔值、字符串等等)地位相同。凡是可以使用值的地方,就能使用函数。

function add(x, y){
  return x + y;
}

// 将函数赋值给一个变量
let op = add;

// 将函数作为参数和返回值
function a(op){
  return op;
}
// python 也支持这一系列操作的
// a(add) 就是函数 add 本身了
// (1,1) 表示要调用 add 这个函数
a(add)(1,1)

6.1.5 函数名的提升

JavaScript 引擎将函数名视同变量名,所以采用 function 命令声明函数时,整个函数会像变量声明一样,被提升到代码的头部。所以,下面代码是不会报错的。

f();

function f() {}

但是如果使用赋值语句定义函数,JS 就会报错。

f();
var f = ()=>{};
// TypeError: f is not defined

上面的代码等于

var f ;
f();
f = function () {};

因此,同时采用 function 命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。

6.2 函数的属性和方法

6.2.1 name 属性

函数的name属性返回函数的名字

function f1(){}
f1.name // f1

如果通过变量赋值定义的函数,name属性返回变量名。

let f2 = function(){}
f2.name // "f2"

如果变量的值是一个具名函数,那么name属性返回的是具名函数的函数名。

let f2 = function myFunction(){}
f2.name  // "myFunction"
name 属性的应用场景

name 属性的一个用处,就是获取参数函数的名字。

let myFunc = function(){};
function test(f){
  console.log(f.name);
}

test(myFunc) 
//"myFunc"

6.2.2 length 属性

函数的 length 属性返回函数签名中需要返回的函数个数

function f(a, b){}
f.length // 2

6.2.3 toString()

返回一个字符串,内容为函数的定义,即便是注释部分也会被返回

6.3 函数作用域

6.3.1 定义

作用域(scope)指的是变量存在的范围。在 ES5 中,JS 只有两宗作用域:全局作用域,变量在整个程序中一直存在,所有地方都可以读取;函数作用域,变量只在函数内部存在。ES6 新增了块级作用域。

函数外部声明的变量就是全局作用域,全局作用域可以在函数内部读取。

var a = 1;
function w(){
  console.log(a);
}
// 1

函数内部定义的变量就是局部变量,不能在函数外部读取。

function w(){
  var a = 1;
}
console.log(a);
// ReferenceError: v is not defined

如果全局变量和局部变量重名,那么在局部作用域内局部变量会覆盖全局变量。这个很好理解。

需要注意的是,var 只有在函数内部定义的才是局部变量,在其他块级作用域内定义的都是全局变量。

if(true){
  var a = 3;
}
console.log(a);
// a

这点也是 let 与 var 的区别,let 定义的变量即便在其他的块级作用域内,依旧是局部变量。

if(true){
  let a = 3;
}
console.log(a);
// RefereceError: a is not defined

6.3.2 函数内部的变量提升

在函数作用域内部,也会出现变量提升。在函数作用域内部,var 声明的变量不管在什么位置,会提升到函数作用域的头部。

function foo(){
  console.log(w);
  var w = 100;
}
foo() // undefined

6.3.3 函数本身的作用域

有点莫名其妙,看完之后觉得都是应该的。

6.4 参数

6.4.1 概述

函数的参数就是函数签名中括号中的那些变量。

6.4.2 参数的省略

在 JS 中函数的参数可以省略,这点 JS 独有的。比如在 Python 中如果省略参数会直接抛出异常,在 JAVA 中不同的参数个数还可以区分不同的方法。

function f(a, b){
  return a + b;
}
f(2) // NaN
f() // NaN

当前不能省略前面的参数,这点是毫无疑问的

6.4.3 传递方式

函数参数如果是原始类型的值(数值、字符串、布尔值),则是值传递。即传入的参数值是原始值的拷贝,在函数内修改参数的值,不会对原始值产生影响。

函数参数如果是容器类型(数组、对象、其他函数等),那么传值的方式是值引用,传进入的是一个地址。如果在函数内修改参数的值,原始值也会随之发生改变。

// Python 
def a(x):
  x[0] = 33

w = [1, 2]
a(w)
w //[33,2]

但是注意,如果函数内部修改的,不是参数对象的属性,而是替换整个参数,这时不会影响到原始值。

def a(x):
  x = [1, 2, 3]

w = ["a", "b"]
a(w)
w
// ["a", "b"]

这是因为,形式参数 x 的值实际是参数 w 的地址,重新对 x 赋值导致 x 指向另一个地址,保存在原地址上的值当然不受影响。

为什么对于原始值是值拷贝,容器类型则是值引用?当然是容器类型东西太多了,为了提高效率才这么做的。

6.4.4 同名参数

这个也算是 JS 特性了。
如果有同名的参数,则取最后出现的那个值。

function f(a, a){
  console.log(a);
}
f(1,2)
// 2

取值的时候,以后面的 a 为准,即使后面的 a 没有值或者被省略,也是以其为准。

f(1) // undefined

调用函数 f 的时候,没有提供第二个参数,a 的取值就变成了 undefined。这时,如果要获得第一个 a 的值,可以使用 arguments 的对象。

function f(a,a){
  console.log(arguments[0]);
}

f(1) // 1

6.4.5 arguments 对象

(1) 定义

由于 JS 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是 arguments 对象的由来。

这个特点可以联想到 shell,shell 的参数也可以使用 1 /2 / $3 ... 来表示。arguments[0] 表示第一个参数、arguments[1] 表示第二个参数...
arguments 对象只有在函数体内部才可以使用。
正常情况下,arguments 对象可以在运行时修改

let f = function(a, b){
  arguments[0] = 3;
  arguments[1] = 2;
  return a + b ; 
}

f(1, 1) // 5

但是在严格模式下,arguments 对象是一个只读对象,修改它是无效的,但是不会报错

function f(a,b){
  "use strict"
  arguments[0] = 1;
  arguments[1] = 2;
  return a + b;
}

f(1,1) // 2

可以通过 arguments 对象的 length 属性,可以判断函数调用时到底带几个参数。

function f(){
  return arguments.length;
}

f(1,2,3) //3
f() // 0
(2) 与数组的关系

阮大对 JS arguments 的描述,让我想起了 Python 的不定参数 *args
arguments 很像数组,但是它是一个对象。数组专有的方法(比如 sliceforEach),不能在 arguments 上直接使用。

如果想让 arguments 对象使用数组方法,真正的解决方法是将 arguments 转化为真正的数组。转换方法有两种:

// 方法一
var args = Array.prototype.slice.call(arguments);
// 方法二
var args = [];
for(var i = 0; i < arguments.length; i++){
  args.push(arguments[i]);
}
(3) callee 属性

callee属性返回它所对应的原函数。
这个属性在严格模式下是禁止使用的。

6.5 函数的其他知识点

6.5.1 闭包

理解闭包(closure),首先要理解作用域。JS 有两种作用域:全局作用域和函数作用域。
函数内部可以直接读取全局变量。
函数外部不可以读取函数内部声明的局部变量。
如果想在函数外部读取函数内部定义的局部变量,就需要使用闭包。

function f1(){
  var n = 999;
  function f2(){
    console.log(n); 
  }
  f2(); // 999
}

f2 在 f1 的内部,f1 内部所有的变量对于 f2 来说都是可见的。反过来,当然不成立。
既然 f2 可以读取 f1 的局部变量,那么只要把f2当作返回值,就可以在f1 函数的外部读取 f1 内部的变量了。

function f1(){
  var n = 999;
  function f2(){
    return n;
  }
  return f2;
}
f1()() // 999

函数 f2 就是闭包,即能够读取其他函数内部变量的函数。由于在 JS 语言中,只有函数内部的子函数能够读取内部变量,因此可以把闭包简单理解为“定义在函数内部的函数”。

闭包有两个作用:

  • 可以读取函数内部的变量;
  • 让这些变量始终保持在内存中,即闭包可以使得它的诞生环境一直存在。
function createIncrementor(start){
  return function (){
    return start++;
  }
}
// i++ 第一次是加0,不知道是为什么
var inc = createIncrementor(5);
inc(); // 5
inc(); // 6

闭包 inc 使得函数 createIncrementor 的内部环境一直存在。原因在于,inc始终在内存中,而 inc的存在依赖于 createIncrementor,因此也始终在内存中,不会在调用结束后,被垃圾回收。

闭包的另一个用处,是封装对象的私有属性和私有方法。

function Person(name){
  var _age;
  function setAge(n){
    _age = n;
  }
  function getAge(){
    return _age;
  }
  return {
    name: name,
    setAge: setAge,
    getAge: getAge,
  }
}

var p = Person("LiMing");
p.setAge(5);
p.getAge(); // 5

注意,外层函数每次运行,都会生成一个新的闭包,这个闭包又会保留外层函数的内部变量,所以内存消耗很大。换成面向对象的语言来说,每次调用 Person对象 都会生成一个新的实例,这些实例有自己的属性,生成的实例多了当然会占内存。

6.5.2 立即调用的函数表达式(IIFE)

在 JS 中,圆括号()是一种运算符,跟在函数名后面表示函数立即调用。
但是需要注意,定义函数之后,不能这样立即调用函数。

function() { /* code */}();
// SyntaxError: Unexpected token (

产生这个错误的原因,function 这个关键字即可以当作语句,又可以当作表达式。

// 语句
function f() {}
// 表达式
var f = function f() {}

为了避免解析上的歧义,JS 引擎规定,如果 function 关键字出现在行首,一律解析成语句。因此,JS 引擎看到行首是 function 关键字之后,认为这一段是函数的定义,不应该以圆括号结尾,就报错了。

解决办法是不要让function 出现在行首,让引擎将其理解为一个表达式。最简单的处理办法是,将函数体放到一个圆括号里。

(function(){}());
// 或者
(function(){})();

这种就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称 IIFE。
注意,上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个 IIFE,可能会报错。

(function(){}())
(function(){})()
// TypeError: (intermediate value)(...) is not a function 

推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果,比如下面三种写法:

var i = function(){}();
true && function(){}();
0, function(){}();

甚至像下面这样写,也是可以的:

!function(){}();
~function(){}();
+function(){}();

对匿名函数使用这种 “立即执行的函数表达式”,有两个目的:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

// 写法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 写法二
(function(){
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
})();

6.6 eval 命令

6.6.1 基本用法

eval 接受一个字符串当作参数,并将这个字符串当作语句执行。

eval('var a = 1');
a // 1

如果参数字符串无法当作语句执行,就会报错。
eval 没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。

var a = 1;
eval('a = 3');
a
// 3

为了避免这种风险,JS 规定,如果使用严格模式, eval 内部声明的变量,不会影响到外部作用域。

(function(){
  'use strict';
  eval('var foo = 123;');
  console.log(foo)
}())
// ReferenceError: foo is not defined

不过即使在严格模式下, eval依然可以读写当前作用域的变量。

(function f(){
  'use strict';
  var foo =1;
  eval('foo = 2');
  console.log(foo); // 2
})()

总之,eval 的本质是在当前作用域之中,注入代码。eval 不利于引擎优化。

6.6.2 eval 的别名调用

引擎在静态代码分析的阶段,根本无法分辨执行的是 eval

var m = eval;
m("var x =1 ");
x // 1

上面的代码中,变量 meval 的别名。静态代码分析阶段,引擎分辨不出m('var x=1 ') 执行的 eval 命令。
为了保证 eval 的别名不影响代码优化,JS 标准规定,凡是使用别名执行evaleval 内部一律是全局作用域。

var a =1;

function f(){
  var a = 2;
  var e = eval;
  e('console.log(a)');
}

f() // 1

引擎只能识别 eval(),eval 的别名调用都属于别名调用。

eval.call(null, '...')
window.eval('...')
(1,eval)('...')
(eval,eval)('...')

7 数组

教程地址:https://wangdoc.com/javascript/types/array.html

7.1 定义

跟 python 一样

7.2 数组的本质

数组的本质是特殊的对象。typeof 运算符会返回数组的类型是 object

typeof [1,2,3] // "object"

数组的键名是按次序排列的一组整数。

var arr = ['a', 'b', 'c'];
Object.keys(arr)
// ["0", "1", "2"]

由于数组成员的键名总是固定的,因此数组不用为每个元素指定键名。JS 语言规定,对象的键名一律为字符串,所以,数组的键名是字符串。之所以可以用数值读取,是因为非字符串的键名会被转化为字符串。

var  arr = ['a', 'b', 'c'];
arr[0] // 'a'
arr['0'] // 'a'

所以,arr 添加新元素除了 push 之后,还可以这样子

let  arr = [];
a[3] = 888
arr
// [undefined, undefined, undefined, 888]

这个操作,Python 没有。

7.3 length 属性

arr.length 
// 返回数组的长度

length 属性可以读写。如果人为把 length 减少到小于数组的长度,那么数组内元素的个数也会减少。

let arr = [1,2,3,4];
arr.length = 2;
arr // [1,2]

清空数组,可以把数组的长度置为0.

arr.length  = 0

如果把length 的长度增大,那么数组内多出来的是空位. 空位和 元素是 undefined 不是一个概念。
Python 数组的长度当然不能这么改变。因为 len 是函数调用。

7.4 in 运算符

in 是用来检查键名是否存在于对象,这个也适用于数组。
要注意,检查的是数组的键名,也就是索引值。

let arr = ["a", "b", "c"]
1 in arr // true
"a" in arr // false

7.5 for...in 循环和数组的遍历

for ... in 切莫当成 Python 的 for...in 。
JS 的 for ... in 索引的是键名。

let arr = ["a", "b", "c"];
for(let i in arr){
  console.log(i);
}
// 0 1 2

此外,数组不仅会遍历数组所有的数字键,还会遍历非数字键。

var a = [1, 2, 3];
a.foo = true

for(let key in a){
  console.log(key);
}
// 0 1 2 foo

遍历数组一般可以使用 for 循环或者 while 循环。
也可以使用 forEach

7.6 数组的空位

当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称数组之间存在空位(hole)。

let a  = [1,,3]
a.length // 3

空位不影响 length 属性。
空位是可以读取的,返回 undefined

var a = [,,,];
a[0] // undefined

使用 delete 删除一个数组成员,会形成空位,并且不会影响 length 属性。

var a = [1,2,3];
delete a[1];

a[1] // undefined
a.length // 3

delete 删除了数组的第二个元素,这个位置就是空位,对 length 属性没有影响。
数组的某个位置是空位,与某个位置是 undefined,是不一样的。如果是空位,使用数组的 forEach,for .. in , Object.keys 方法进行遍历,空位都会被跳过。

var a  = [,,,];

空位就是数组没有这个元素,所以不会遍历到,而 undefined 则表示数组有这个元素,值是 undefined,所以遍历不会跳过。

7.7 类似数组的对象

如果一个对象的所有键名都是正整数或0,并且有 length 属性,那么这个对象就很像数组,语法上称为 “类似数组的对象”。

var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
}

obj[0] // 'a'
obj.length // 3

类似数组的对象,当然不能使用 push 方法了,当然不能使用 length 删减元素了。
典型的 “类似数组的对象”是函数的arguments对象,以及大多数 DOM 元素集,还有字符串。

// arguments 对象
function args(){
  return arguments
}
let arrayLike = args('a', 'b');
arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false

// DOM 元素集
let ele = document.getElementsByTagName('h');
ele.length // 0
ele[0] // undefined

// 字符串
let str = "abdc"
str.length // 4
str instanceof Array // false

数组的 slice 方法可以将“类似数组的对象”变成真正的数组。

let arr = Array.prototype.slice.call(arrayLike)'

还有一个办法,通过call 把数组的方法放到对象上面。

let arrLike = {
  0: 1,
  1:22,
  2:33,
  length:3,
}
function print(value, index){
  console.log(index + ":" + value);
}
Array.prototype.forEach.call(arrayLike, print);

arrayLike 是个类数组的对象,本来不可以使用数组的 forEach() 方法的,但是通过 call(),可以把 forEach() 嫁接到 arrayLike 上面调用。

Python 也是有类似行为的,一个类如果加上某些特定的双下划线方法就可以使用一些 Python 方法。

字符串也是类似数组的对象,所以也可以使用 Array.prototype.forEach.call 遍历。

Array.prototype.forEach.call('abc', function (chr){
  console.log(chr);
});
// a b c

这种方法比直接调用数组原生的 forEach 要慢,所以最好还是将类似数组的对象转为真正的数组,然后再调用数组的 forEach 方法。

let arr = Array.prototype.slice.call('abc');
arr.forEach((chr)=>{
  console.log(chr);
});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,093评论 0 13
  • 1、标识符 标识符是指变量、函数、属性的名字,或函数的参数。 格式规则: 第一个字符必须是一个字母、下划线(_)或...
    霜天晓阅读 686评论 0 0
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,082评论 0 21
  • 本章内容 语法 数据类型 流控制语句 理解函数 3.1 语法 3.1.1 区分大小写 区分大小写 3.1.2 标识...
    闷油瓶小张阅读 706评论 0 0
  • 晚上看了《一站到底》的年度总决赛,正常是周一晚上播的。虽然看标题就知道最后是谁夺冠了,但看的过程依然紧张到肌肉绷紧...
    西楚黎明阅读 375评论 0 0