基础知识

1. 标识符

标识符(identifier)是变量、函数、属性、参数的名称,需遵循以下格式:

  1. 首字符必须为字母,或者下划线(_)或者$;
  2. 所有字符必须为字母、数字、下划线或$符号。
  • 上述“字母”可以是 ASCII 或 Unicode 编码(所以中文也可做标识符),但不推荐使用。
  • 推荐使用驼峰命名。

2. 变量声明

2.1 var 声明

2.1.1 函数中使用 var

声明变量,在函数中使用 var 时,函数执行时创建变量,执行后变量销毁

function test() {
    var a = 2;
}
test();
console.log(a); // error

变量前不使用任何关键字时,将创建全局变量。

function test() {
    a =  2;
}
test();
console.log(a); // 2

上述代码中,函数执行时创建变量a,函数执行完成后仍能访问到a,但不推荐不使用任何关键字直接声明变量

2.1.2 var 的变量提升

所谓变量提升(hoisting),就是在声明变量时,会把声明自动放在(代码或函数的)顶部。

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

上述代码等同于:

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

即代码var age = 26 可看作两部分:var ageage = 26 前者为声明变量,后者为变量赋值;声明变量时会被提升到(代码或函数)的顶部。这就是变量提升。

2.2 let 声明

2.2.1 let 作用域

letvar类似但有一些不同,最重要的区别是 let 是块级作用域(比如在if中、在for中),var 是函数级作用域。

// 在块级作用域中使用 var
if(true) {
    var name = 'Matt';
    console.log(name);
}
console.log(name); // Matt

// 在块级作用域中使用 let
if(true) {
    let age = 26;
    console.log(age);
}
console.log(age); // ReferenceError: age is not defined
2.2.2 let 变量提升

let 也存在变量提升,但和 var 不同的是, let 提升后,在未赋值前访问不到(被放入'temporal dead zone'),直到被赋值为止。所以在赋值前访问 let 声明的变量会报错,而在赋值前访问 var 声明的变量得到 undefined一句话,let 变量提升时访问不到

console.log(name); // udefinde
var name = 'Paul';

console.log(age); // ReferenceError: age is not defined
let age = 26;
2.2.3 let 不能全局声明

var 声明变量,变量会作为属性赋给window对象,let 则不会。

var name = 'Matt';
console.log(window.name);// 'Matt'

let age = 26;
console.log(window.age); // undefined
2.2.4 let 不能重复声明

var 允许重复声明变量,而 let 不能重复声明变量。

这是由于var的变量提升作用,js会把所有的重复声明都变成单一的一个声明。

var a = 1;
var a = 2;

上述代码等同于:

var a;
a = 1;
a = 2;

所以var可以重复声明变量;但是再多人协作开发时,重复声明一个用途不同的变量,会导致各种问题。

let 很好的解决了重复声明问题。

let age = 26;
let age = 27; // Uncaught SyntaxError: Identifier 'age' has already been declared
2.2.5 for循环中的let

for 循环中用 var 声明迭代变量时:

for (var i = 0; i < 5; i++) {
    // do somthing
}
console.log(i); // 5

for 循环中用 let 声明迭代变量时:

for (let i = 0; i < 5; i++) {
    // do somthing
}
console.log(i); // ReferenceError: i is not defined

常见问题:for 循环中使用 setTimeout :

for (var i = 0; i < 5; i++) {
    setTimeout(()=>{console.log(i)},0)
}
//5, 5, 5, 5, 5
setTimeout异步代码,for 循环为同步代码,即使等待时间为0,也要等 for 循环执行完后才会执行 setTimeout 中的函数;此时i已经等于5了,所以全部输出5。

let 的出现很好的解决了这一问题:

for (let i = 0; i < 5; i++) {
    setTimeout(()=>{console.log(i)},0)
}
//1, 2, 3, 4, 5
let循环中,每循环一次会创建一个块级作用域,循环结束后,有5个块级作用域,每个块级作用域中的 i 都有不同的值,所以输出1,2,3,4,5

2.3 const 声明

constlet 一样,也是块级作用域;但有一个重要区别—声明时必须赋值,而且不可重新赋值,更不可重复声明

const age = 26;
age = 43; // TypeError: assignment to a constant
// const 是块级作用域
const name = 'Matt';
if (true) {
    const name = 'Mike';
}
console.log(name); // 'Matt'

如果 const 声明了一个对象(引用类型)数据,该对象可以修改其属性(因为实体指向的是对象的地址)。

const p = {};
p.name = "Matt";

3. 数据类型

6种简单数据类型:Undefined Null Bollean Number String Symbol

1种复杂数据类型:Object( function 本质上也是一个 Object )

3.1 typeof 操作符

用于判断数据的类型,返回值有:"undefined", "boolean", "string", "number", "object", "function", "symbol"

注意:

  • typeof null 返回 "object"null是一个空对象引用(地址指向一个不存在的对象);
  • typeof 本身是一个操作符,而非一个函数

3.2 Undefined

变量已声明但未赋值,该变量将会有一个特殊的值—undefined。

let msg;
conso.log(msg == undefined); // true

上述代码等同于:

let msg = undefined;
conso.log(msg == undefined); // true

undefinedReferenceError: xxx is not defined,前者是声明但为赋值,后者是压根就没声明。但使用 typeof 操作符判断一个未声明的值,返回 undefind

let a;
console.log(a);// undefined
consol.log(b); // ReferenceError: b is not defined
typeof(b); //undefined

3.3 Null

null是一个空对象指针,所以typeof null 返回 "object"

undefinednull 的衍生值, ECMA-262 规定二者表面上相等:

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

3.4 Boolean

Boolean 有 truefalse 两种值,还可以通过内置 Boolean() 方法将其他值转换为Boolean

let msg = "Hello";
console.log(Boolean(msg)); // true

Boolean() 方法对应的返回值:

数据类型 true false
String 任何非空字符串 空字符串
Number 非0数值 0, NaN
Object 任何对象 null
Undefined / undefined

这种转换有助于理解一些条件语句中的判断为何能生效,比如:

let msg = "Hello";
if(msg) {
    console.log("Value is true"); // 该语句能够输出
}

因为在条件语句中,会自动通过Boolean() 方法将判断条件转换为Boolean

3.5 Number

js中的Number包括整数型和浮点型,整数型支持8进制和16进制的表示方式,8进制必须以0开头,16进制必须以0x开头。

3.5.1 浮点类型

对于下面的写法,js会将浮点型自动转为整数类型以节省内存

let f1 = 1.;
let f2 = 10.0;

支持科学计数法:

let floatNum = 3.125e7;  // equal to 31250000

浮点类型精确到小数点后17位,从而导致了其运算会出现极小的不精确,如 0.1 + 0.2 会得到 0.30000000000000004。所以要避免数值计算作为条件语句的判断条件:

if (a + b = 0.3) { // 要避免这样写
    // do somthing
}
3.5.2 取值范围

由于内存限制,Number类型有自己的取值范围,可以分别用 Number.MIN_VALUENumber.MAX_VALUE 得到最小和最大取值。如果数值运算结果超出取值范围,则会得到Infinity-Infinity 表示正无穷和负无穷。

可以用isFinite()判断数值是否超限。

let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result));  // false, 表示超限
3.5.3 NaN
  • Not a Number,表示数值计算不能返回 Number 类型的数据,比如0做除数时;
  • NaN 与任何数值运算都会得到 NaN
  • NaN 不等于任何值,包括 NaN
  • 可以用 isNaN() 判断是否为 NaN
console.log(NaN == NaN);  // false
console.log(isNaN(NaN));     // true
console.log(isNaN(10));      // false - 10 is a number
console.log(isNaN("10"));    // false - can be converted to number 10
console.log(isNaN("blue"));  // true - cannot be converted to a number
console.log(isNaN(true));    // false - can be converted to number 1
3.5.4 数值转换

有三种方法可以将其他类型转为Number类型:Number() parseInt() parseFloat() ;

下面是几个特殊的转换:

Number(null); // return 0
Number(true); // return 1
Number(""); // return 0
Number("Hello"); //return NaN
parseInt("123aabbcc"); // return 123
parseInt(""); // return NaN
parseInt(22.5); // return 22
parseFloat("22.34.5") // return 22.34
parseFloat("1234blue");  // return 1234 - integer

3.6 String

表示0到多个连续的16位Unicode编码的字符。 可以使用单引号、双引号、反引号(``)(ES6)

3.6.1 字符串的不可变性

ECMAScript中的字符串是不可变的,也就是说字符串一旦创建,它们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后用另一个包含新值的字符串填充该变量。

不可变的原因是字符串是位于堆内存上的,想要改变则需要销毁原来的对象,重新创建一个新的。
3.6.2 toString() & String()

toString()String() 作用是将其它数据类型转换为 String 类型。几乎每种类型都自带 toString() 方法,但 nullundefined 除外。把 nullundefined 转换为 String 类型需要使用String() 方法。

String(null) // return "null"
String(undefind) // return "undefined"

还可以通过以下方式将其他类型转换为字符串:

let num = 11;
let numStr = "" + num; // "num"
3.6.3 模板字符串

可以使用反向单引号(``)是字符串自动换行,该方法特别适合写html模板。

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

通过反向单引号还可以对字符串进行差插值,语法:${}

let value = 5;
let result = 10;
// 不使用插值
let str = value + '乘以2等于' + result;
// 使用插值
let str2 = `${value}乘以2等于${result}`;

插值中还可以调用函数:

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

插值可多次调用

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

更高级的用法(嵌套模板):

const class = `header ${isLargeScreen()? '' : 
   `icon-${item.isCollapsed ? 'expander' : 'collapser'}`}`;
3.6.5 带标签的模板字符串

(没太看懂)

let a = 6;
let b = 9;
function simpleTag(strings, aValExpression, bValExpression, sumExpression) {                 console.log(strings);  
  console.log(aValExpression);  
  console.log(bValExpression);  
  console.log(sumExpression);  
  return 'foobar';
}
let untaggedResult = `${ a } + ${ b } = ${ a + b }`;
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15

3.7 Symbol

ES6 中加入了 Symbol 数据类型,表示独一无二的值。该数据类型通常用来作为一个对象属性的键值

Symbol 是惟一的、不可变的。

3.7.1 基本使用

通过 Symbol() 函数进行实例化。

let sym = Symbol();
console.log(typeof sym);  // symbol

3.8 Object

Object 是非特定数据和功能的组,通过 new 操作符进行创建。

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

  • constructor — 构造函数,用来创建一个 Object 实例
  • hasOwnProperty(propertyName) — 给定的属性是否存在(不能判断原型链里的属性),例:o.hasOwnProperty("name")
  • isPrototypeof(object)— 判断该对象是否为另一个对象的原型。
  • propertyIsEnumerable(propertyName)— 判断给定的属性是否可以用 for-in 语句遍历。
  • toLocaleString() — 返回特定格式的String

    const date = new Date();
    date.toLocaleString('zh');    // 2020/3/25 下午7:07:27
    date.toLocaleString('en');    // 3/25/2020, 7:07:27 PM
  • toString() — 将对象转为字符串
  • valueOf() — 通常返回一个字符串

4. 运算符

4.1 ++a和a++

前者是先计算再使用

let age = 29;
let anotherAge = --age + 2;
console.log(age);         // 28
console.log(anotherAge);  // 30

后者是先试用再计算

let num1 = 2;
let num2 = 20;
let num3 = num1-- + num2;
let num4 = num1 + num2;
console.log(num3);  // 22
console.log(num4);  // 21

4.2 幂运算

在ES7中,可以使用 ** 运算符代替 Math.pow() 进行幂运算。

console.log(Math.pow(3, 2);     // 9
console.log(3 ** 2);            // 9

4.3 == 和 ===

== 会对两边的类型做自动转换,使两边的类型一致;=== 不会对两边的类型做自动转换。

推荐使用 ===

let res1 = ("55" == 55); // true
let res2 = ("55" === 55); // false
let res3 = ("55" != 55); // false
let res4 = ("55" !== 55); // true
null == undefined; // true
null === undefined; // false

4.4 条件运算符

let max = (num1 > num2) ? num1 : num2;

5. 语句

5.1 循环

可以这样创建一个死循环:

for (;;) {
    // do something
}

标签语句:给语句加一个标签,在 breakcontinue 时可以通过这个标签来引用该语句。标签通常在嵌套循环中使用。

outer:
for (let i = 0; i < 10; i++) {
    inner:
    for (let j = 0; j < 5; j ++) {
        if (i === 2) {
            break inner;
        }
        if ( i=== 4) {
            break outer;
        }
    }
}

5.2 with 语句

with 语句可以给某个对象创建一个特定的使用范围。语法:with (expression) statement;

例如:

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


芒果香蕉
4 声望1 粉丝