JS语言基础

17 阅读12分钟

JavaScript 语言基础

语法

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

  • 标识符(变量、函数、属性或函数参数的名称)

    • 第一个字符必须是字母、下划线(_)、或美元符号($);
    • 剩余字符可以是 字母、数字、下划线 和 美元符号($);
    • 标识符使用驼峰大小写形式命名:firstCard、myCar等;
    • 关键字、保留字、true\false、null不能作为标识符
  • 注释:(采用c语言风格的注释)

    // 单行注释:xxxxx
    
    /**
     * 多行注释:xxxxxxx
    */
    
    
  • 严格模式

    对整个脚本启用严格模式,在脚本开头加上 "use strict"
    
    单独指定一个函数使用严格模式:
    function doSomething() {
      "use strict";
    }
    
  • 语句

    ECMAScript 中的语句以分号结尾。可以省略分号,但不推荐。加上分号有助于防止省略分号带来的问题,如:

    1. 避免输入内容不完整;
    2. 便于开发者删除空行来压缩代码,避免语法错误;
    3. 有助于在某些情况下提升性能。解析器会尝试在合适的位置上加上分号以纠正语法错误。

    控制流程语句,通常是由多条语句组成,也可能是单条语句,都推荐用代码块 花括号{} 包裹,便于阅读和减少出错的可能性。

变量

ECMAScript 中的变量是松散类型,可以用于保存任何类型的数据,每个变量是用于保存任意值的命名占位符。有3个关键字可以声明变量:var、let、const。

var、let 声明的变量,如果没有初始化赋值,默认为 undefined

const 声明变量的同时,必须初始化变量,且不可被改变。如果被定义的变量是一个引用类型的数据,则这个引用地址不可被改变,即对象不能被整体覆盖,但对象内部的属性可以被修改。

如果一个变量声明省略了声明关键字,会被var隐式声明为全局变量。(不推荐的做法!不方便变量的管理)。在严格模式下,给这样声明的变量赋值会抛出ReferenceError。

var

  • 作用域:函数作用域

    var 声明的变量,会成为包含这个变量的函数的局部变量。当这个变量不被下级或外部持有时(没有形成闭包),函数调用完毕,该变量就会被回收

  • 声明提升

    用var 声明的变量,会自动被提升到函数作用域的顶部,并会对多次重复声明的变量进行合并,值为 undefined 。所以在声明之前调用该变量,不会报错,但值为 undefined。

  • 全局声明

    var 声明全局变量时,该变量会成为 window 对象的属性,可以用 in 操作符判断

let

  • 作用域:块级作用域

    块级作用域 相当于 函数作用的子集。let 声明的变量,作用域范围是被花括号{ } 包裹的代码块内,代码块外无法访问到其内声明的变量。

  • 同一作用域内,不能重复声明同名变量。但可以该代码块内的嵌套代码块内声明同名变量,因为javascript引擎会记录用于声明变量的标识符 及其 所在的块级作用域。

  • 变量提升

    不会在声明的作用域内被提升,有暂时性死区。javascript在解析代码时(执行上下文的创建阶段),会注意到出现在块后面的 let 声明的变量,该变量在声明之前都不能被 调用,否则报错,即为暂时性死区

  • 全局声明

    用 let 声明的全局变量,不会成为 window 对象的属性!相应变量的生命周期 与 页面的生命周期相同,所以需要保证同一个页面避免重复let声明同一个变量,否则报错。

for 循环中的变量声明

// var 声明
for(var i = 0; i < 5; i++) {
  setTimeout(() => {
     console.log(i);  // 5次都是5,因为setTimeout是异步的,会等for循环结束后再执行,所以i的值已经是5了。 
  }, 0)
}

// IIFE匿名自执行函数改造
for(var i = 0; i < 5; i++) {
  (function(i){
    setTimeout(() => {
     console.log(i);  // 0 1 2 3 4
    }, 0)
  })(i) // 将当此循环的变量作为参数,值传入。
  
}
// let 声明
for(let i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i) // 0 1 2 3 4 
  }, 0
}
             
使用let声明迭代变量时,javascript引擎会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量,是当前循环变量。
if(true) {
  var age = 30;
  let age = 26;   // 报错 SyntaxError age已经被声明过,不会因为是var还是let声明的同名变量而产生影响
}

if(true) {
  let age = 26;   
  if(age < 18 {
    let age = 18;  // 不会报错,两个age处在不同的代码块内 
  }
}

const

const 与 let 基本相同,唯一重要区别是const声明变量的同时,需要初始化赋值变量,且后续不可修改该变量。

  • 作用域:块级作用域

不能用const 声明for循环的变量,但可以声明 for-of、for-in循环

for(const i = 0; i < 10; i++) {}  // 报错!i会自增

for(const key in {a: 1, b: 2}) {
  console.log(key);  // 只是在每次迭代循环时,用const声明一个key变量
}

for(const value of [1, 2, 3, 4]) {
  console.log(value);  // 只是在每次迭代循环时,用const声明一个key变量
}

ECMAScript 6 增加了 let const 提供了更精确的声明作用域和语义支持,推荐不再使用 varconst 优先使用,let 次之。

数据类型

ECMAScript的数据类型分为 原始数据类型引用数据类型

原始数据类型有 6 种:

  • Number
  • String
  • Boolean
  • Null
  • Undefined
  • Symbol (es6新增)

Number

Number类型使用IEEE 754 格式表示整数和浮点数,不同的数值类型(整数/浮点数)也有相应的不同字面量格式:十进制、八进制、十六进制

IEEE 754 是一个由 IEEE(Institute of Electrical and Electronics Engineers)制定的浮点数运算标准,广泛用于计算机系统中表示实数(如 float、double)。

常见的浮点数格式

类型总位数符号位指数位尾数位
单精度(float)32 位1823
双精度(double)64 位11152

🧮 示例:32位浮点数(二进制)

一个单精度浮点数(float)可能表示如下:

0 10000001 01100000000000000000000
| |--------|---------------------|
|   指数    尾数(23 位)
符号位

表示的是一个正数(符号位 0),指数为 129(即实际指数为 129 - 127 = 2),尾数是 1.75(二进制),
单精度的固定偏移量是:2^7 - 1 = 127
双精度的固定偏移量是:2^10 - 1 = 1023
1. 浮点数

存储浮点数使用的内存空间是存储整数值的两倍,所以ECMAScript总是会想方设法将值转为整数。

对于非常大或者非常小的浮点值,可以使用科学计数法表示。浮点数的精度最高可达17位小数,因此使用中会存在误差,如:0.1 + 0.2 != 0.3

因此永远不要测试某个特定的浮点值。

2. 值的范围

由于内存的限制,ECMAScript能表示的值的范围有限。能表示的最小值保存在 Number.MIN_VALUE , 在大多数浏览器中是 5e-324 ;最大值保存在 Number.MAX_VALUE中,在多数浏览器中是1.797 693 134 862 315 7e+308 ;

如果计算的值超出了这个范围 Number.MIN_VALUE ~ Number.MAX_VALUE , 就会自动转换为一个特殊的值 Infinity 或者 -Infinity ,都不能在进行任何运算。可以使用 isFinity() 方法判断。

使用 Number.NEGATIVE_INFINITY 可以获取 infinity

Number.POSITIVE_INFINITY 获取 -infinity

3. NaN

NaN 是一个特殊的数值,表示 不是数值 (not a number),用于表示本来要返回一个数值,但失败了。

NaN 的几个独特属性:

  • NaN 与任何值都不等,包括 NaN 本身;
  • NaN 与任何值进行操作,结果都是 NaN;
  • 可使用 isNaN() 判断任意数据是否是NaN,会将接受到的值 先转为数值类型,再判断。如果接受的是一个对象类型的值,首先会调用对象的 valueOf() 方法,然后再确定返回的值是否可转为数值,若不能,再调用 toString() 方法,并测试其返回值。
4. 数值转换

有三个方法可以将非数值转换为数值:Number()parseInt()parsefloat()

方式一: Number()

Number() 方法是强制转换,可以用于任意数据类型。

遵循的规则:

  • 布尔值,true转为1,false转为0;
  • 数值,直接返回;
  • null,返回 0;
  • undefined,返回 NaN;
  • 字符串,应用以下规则:
    • 如果字符串包含数值,包括前面带 + - 的情况,转为十进制数值;
    • 如果字符串包含有效浮点值格式,如 '1.01', 转为浮点数;
    • 如果字符串包含有效的十六进制格式,如 "0xf" ,会转为与该十六进制值对应的十进制整数值;
    • 空字符串,返回 0;
    • 包含除以上情况之外的字符串,返回NaN。
  • 对象。先调用 valueOf() 方法,按照上述规则进行转换,如果是 NaN , 再调用 toString() 方法,按照字符串的转换规则进行转换。

方式二:parseInt()

主要用于将字符串转换为数值。Number() 方法更专注于是否包含数值模式。

字符串前面的空格会被忽略,从第一个非空格字符开始转换,如果第一个字符不是数值、加号或减号,直接返回 NaN;意味着空字符也会返回NaN parseInt("") // NaN 这点于Number() 方法不同!!

依次检测每个字符,直到字符串末尾,或碰到非数值字符,如 "123blue" 被转换为 123 , "11.2" 被转换为 11 , 小数点也不是有效的字符。

let num = parseInt("123blue");    // 123
let num = parseInt("");    // NaN
let num = parseInt("0xA");    // 10  解释为十六进制整数
let num = parseInt("22.4");    // 22
let num = parseInt("70");    // 70  解释为 十进制整数

// parseInt() 也接受第二个参数,用于指定进制数, 默认是十进制
let num = parseInt("0xAF", 16);    // 175
let num = parseInt("AF", 16);    // 175  如果提供了进制数,前面的0x可以省略
let num = parseInt("AF");    // NaN  
let num = parseInt("10", 2);    // 2  按二进制解析
let num = parseInt("10", 8);    // 8  按八进制解析

方式三: parseFloat()

parseInt() 用法相似,都是从字符串的第0位开始,检测每个字符,直到字符串末尾,或者解析的一个无效的浮点数值字符为止,第一次出现的小数点有效,第二次出现的小数点无效。

只能解析十进制,不能指定进制数。

let num = parseFloat("123blue");    // 123  按整数解析
let num = parseFloat("0xA");    // 0
let num = parseFloat("22.5");    // 22.5
let num = parseFloat("22.32.4");    // 22.32
let num = parseFloat("0908.4");    // 908.4
let num = parseFloat("3.123e7");    // 31230000

String

字符串可以用单引号'' 、双引号""、反引号标识;

字符串是不可变的,一旦创建了,他们的值就不能再被改变,如果要修改原来某个变量的字符串值,就必须先销毁原来的字符串,然后将新值的另一个字符串保存到该变量。

转换为字符串

转换为字符串的方法有两个:toString()String()

toString() 方法:

几乎所有值都有toString() 方法,除了 undefinednull , 该方法返回当前值的字符串等价值

多数情况下,toString() 不接受任何参数,如果是数值调用这个方法时,可以传入一个底数参数,标识进制,默认情况是十进制

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

因为 nullundefined 没有 toString() 方法,所以如果不确定传入的值是否是这两个值时,可以用第二种方式: String() 强制转换,遵循如下规则:

  • 如果值有 toString() 方法,调用该方法转换;
  • 如果值是 null , 返回 "null"
  • 如果值是 undefined ,返回 "undefined"
模板字面量

es6新增的使用模板字面量定义字符串。保留了换行符,可以跨行定义字符串;

可以使用字符串插值,插入javascript语句和变量。字符串使用${} 插入js表达式;

使用模板字面量标签函数:

模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。

标签函数 会接收被插值记号分隔后的模板和对每个表达式求值的结果。

let a = 1;
let b = 2;
let tag = `sum: ${ a } + ${ b } = ${ a + b }`

// 标签函数
function simpleTag(str, ...vals) {
    console.log(str)  // ['sum: ', ' + ', ' = ', '', raw: Array(4)]  被插值记号分割的字符串数组
    console.log(vals)  // [1, 2, 3]  表达式的计算结果
}

console.log(tag);   // "sum: 1 + 2 = 3"
simpleTag`sum: ${ a } + ${ b } = ${ a + b }`  // 调用模板字面量标签函数

Boolean

布尔值有两个字面量值:true false , 并严格区分大小写。

布尔值类型转换:

1750991555599转存失败,建议直接上传图片文件

notes: 除了一下 个值,其余值都是转为true

false
0NaN
空字符串(""null
undefined

Null

Null类型也只有一个字面量值:null , 表示一个空对象指针。使用 typeof 判断 null 的类型时,返回 "object"

在定义一个将来要保存对象值的变量时,建议用 null 来初始化

Undefined

唯一的字面量值: undefined ,由 null 派生而来,ECMAScript定义他们表面上是相等的,即:undefined == null // true

定义一个变量时,没有初始化,默认的初始化值是 undefined

Object

Object 类型就是 一组数据和功能的集合 ,通过 new 关键字来实例化对象

Symbol

Symbol 类型是 es6新增的数据类型,符号类型。

符号是原始值,是唯一 且 不可变的。

用途:

确保对象属性使用唯一标识符,不会出现属性冲突的危险。同样,可以使用Symbol来创建唯一记号,用作非字符串形式的对象属性。

基本用法

使用Symbol() 函数直接初始化,注意:不能使用 new !new 关键字是实例化对象的,Symbol是原始类型。

只要创建 Symbol()实例并将其 用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。

let sym = Symbol();
let sym1 = Symbol();
console.log(sym == sym1);  // false
console.log(sym);  // Symbol()

// 可以传入一个字符串参数作为对符号的描述。但这个描述与符号的定义和标识完全无关,只是方便将来调试代码。
let sym2 = Symbol("foo");
let sym3 = Symbol("foo");
console.log(sym2 == sym3);  // false
console.log(sym2);   // Symbol("foo")
Symbol.for()

全局符号注册表。

Symbol.keyfor()

在全局符号注册表中查找相关描述的符号,返回描述字符串