# 变量

变量意味着一小块存储数据的内存空间,javascript是弱类型语言,在开辟变量存储空间时,不定义空间将来的存储数据类型,可以存放任意类型的数据。而Java则需要在开辟变量存储空间时,定义了指定数据类型的空间来存储数据,该空间只能存储固定类型的数据。声明变量的语法为:

var 变量名 = 初始化值;

注意,Javascript每行如果只有一条语句,可以省略";"。但是如果有多条语句,不能省略";"。

# 命名规则

  1. 变量命名必须以字母或是下标符号”_”或者”$”为开头。
  2. 变量名长度不能超过255个字符。
  3. 变量名中不允许使用空格。
  4. 不用使用脚本语言中保留的关键字及保留符号作为变量名。
  5. 注意javascript中变量区分大小写,而html中的变量是不区分大小写的。

# 变量作用域

javascript的作用域跟其他语言不太一样,如果使用var声明变量,那么它没有块级作用域,只有函数作用域。函数作用域指的是变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。看下面实例:

<html>

<head></head>

<body>
    <script type="text/javascript">
        var scope = "global";
        function testScope() {
            console.log(scope);
            var scope = "local"
            console.log(scope);
        }
        testScope();
        console.log("-------------------")

        if (true) {
            var name = "local";
            console.log("inside:"+name)
        }
        console.log("outside:"+name);
        console.log("-------------------")

        var name2 = "global";
        if (true) {
            console.log(name2);
            var name2 = "local";
            console.log("inside:"+name2)
        }
        console.log("outside:"+name2);
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

为了解决块级作用域,在ES6中引入了let和const关键字,可以声明块级作用域变量:

<html>

<head></head>

<body>
    <script type="text/javascript">
        if (true) {
            let a = 1;
            const b = 2;
        }
        console.log(a);
        console.log(b);
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在函数体外的所有变量不管有没有加var保留字都是全局变量。在函数体内加var的则为局部变量,不加var的则为全局变量:

<html>

<head></head>

<body>
    <script type="text/javascript">
        var testVar = 1;//全局变量 
        function aa() {
            console.log(testVar);
            testVar = 2;     //testVar为函数内部没有用var声明的全局变量 
            console.log(testVar);
        }
        aa();
        console.log(testVar)//结果为2 
        console.log("----------------------");

        testVar2 = 1;//全局变量 
        function bb() {
            console.log(testVar2);//结果为undefined 
            var testVar2 = 2;     //testVar为函数内部用var声明的局部变量 
            console.log(testVar2);//结果为2 
        }
        bb();
        console.log(testVar2)//结果为1
        console.log("----------------------");

        testVar3 = 1;//全局变量 
        function cc() {
            var testVar3
            console.log(window.testVar3);//结果为1
            testVar3 = 2;     //testVar为函数内部用var声明的局部变量 
            console.log(testVar3);//结果为2 
        }
        cc();
        console.log(testVar3)//结果为1 
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

在Javascript中,函数也是对象,对象中有些属性我们可以访问,有些不能访问,这些不能访问的属性仅供Javascript引擎存取,比如[[scope]],这个指的是我们所说的作用域,其中存储执行上下文对象的集合,这个集合呈链式链接,称为作用域链:

<html>

<head></head>

<body>
    <script type="text/javascript">
        testVar = 1;
        function fun1(){
            var testVar = 2;
            function fun2(){
                console.log(testVar)
            }
            function fun3(){
                var testVar = 3;
                console.log(testVar)
            }
            fun2();
            fun3();
        }
        fun1();
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

可使用with来临时扩展作用域链,将语句中的对象添加到作用域链头部:

<html>

<head></head>

<body>
    <script type="text/javascript">
        person = { name: "xie", age: 23, height: 170, friend: { name: "wang", age: 23 } };
        with (person.friend) {
            console.log(name);
        }
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

通过上面的知识来分析一个实例的运行结果:

<html>

<head>
    <script type="text/javascript">
        function buttonInit() {
            for (var i = 1; i < 4; i++) {
                var b = document.getElementById("button" + i);
                b.addEventListener("click", function () { alert("Button" + i); }, false);
            }
        }
        window.onload = buttonInit;
    </script>
</head>

<body>
    <button id="button1">Button1</button>
    <button id="button2">Button2</button>
    <button id="button3">Button3</button>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 变量类型

  1. 基本数据类型:
    1. number(数字):整数/小数/NaN(not a number 一个不是数字的数字类型)
    2. string(字符串):字符串"abc" "a" 'abc'
    3. boolean(字符串): true和false
    4. null:一个对象为空的占位符
    5. undefined:未定义,如果一个变量没有给初始化值,则会被默认赋值为undefined
    6. Symbol:ES6新加入类型,对象属性名都是字符串容易造成属性名冲突。为了避免这种情况的发生,ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。
  2. 引用数据类型:object(对象,Function,Array)

# Number

Number类型表示我们通常意义上的“数字”,这个数字大致对应数学中的有理数。Number中有几个特殊值:

  • NaN,非数字值的特殊值,可使用isNaN()来判断。
  • Infinity,正无穷大
  • -Infinity,负无穷大

注意,非整数的Number类型无法使用==或===来判断:

<html>
<head>judge value</head>
<body>
    <script type="text/javascript">
        console.log( 0.1 + 0.2 == 0.3);
        console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
    </script>
</body>
</html>
1
2
3
4
5
6
7
8
9

# Boolean

Boolean 类型有两个值, true 和 false,它用于表示逻辑意义上的真和假。

# String

string用于表示文本数据,最大有效长度是2^53 - 1,但是它的意义并非是"字符串",而是字符串的UTF16编码,常用的操作charAt、charCodeAt、length都是针对该编码的,字符串最大长度,实际上是受字符串的编码长度影响的。

JavaScript 中的字符串是永远无法变更的,一旦字符串构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。

JavaScript 字符串把每个 UTF16 单元当作一个字符来处理,所以处理非 BMP(超出 U+0000 - U+FFFF 范围)的字符时,你应该格外小心。

# null和undefined

Undefined类型表示未定义,它的类型只有一个值,就是undefined。任何变量在赋值前是Undefined类型、值为undefined,一般我们可以用全局变量 undefined(就是名为 undefined 的这个变量)来表达这个值,或者 void 运算来把任意一个表达式变成 undefined 值。但是JavaScript设计时把undefined设计为一个变量,而并非是一个关键字,这是 JavaScript 语言公认的设计失误之一,我们为了避免无意中被篡改,我建议使用 void 0 来获取 undefined 值。

null 跟 undefined 有一定的表意差别,Null 表示的是:“定义了但是为空”。在实际编程时,我们一般不会把变量赋值为 undefined,这样可以保证所有值为 undefined 的变量,都是从未赋值的自然状态。Null 类型也只有一个值,就是 null,它的语义表示空值,与 undefined 不同,null 是 JavaScript 关键字,所以在任何代码中可以放心用 null 关键字来获取 null 值。

注意如果在全局条件下undefined是不可以被修改的。在局部条件下undefined在某种情况下是可以被修改的。看示例:

<html>
<head>change undefined value</head>
<body>
    <script type="text/javascript">

        var a = void 0;
        var b;
        if (a === b) {
            console.log("a === b");
        }
        console.log(typeof undefined)
        undefined = '1';
        console.log(typeof undefined)
        console.log(undefined)

        function abc() {
            console.log(typeof (undefined));
            var undefined = 'foo';
            console.log(undefined);
            console.log(typeof (undefined));
        }
        abc()
    </script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# Symbol

Symbol 是 ES6 中引入的新类型,它是一切非字符串的对象 key 的集合,在 ES6 规范中,整个对象系统被用 Symbol 重塑。Symbol 可以具有字符串类型的描述,但是即使描述相同,Symbol 也不相等,它表示一个独一无二的值。

<html>

<head>Symbol value</head>

<body>
    <script type="text/javascript">
        var s1 = Symbol("mysymbol");
        var s2 = Symbol("mysymbol");
        console.log(s1);
        console.log(s2);
        if(s1 === s2){
            console.log("s1 == s2");
        }else{
            console.log("s1 != s2");
        }

        var o = new Object 
        o[Symbol.iterator] = function () { 
            var v = 0 
            return { 
                next: function () { 
                    return { value: v++, done: v > 10 } 
                } 
            } 
        }; 
        for (var v of o) console.log(v); // 0 1 2 3 ... 9
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# Object

Object 是 JavaScript 中最复杂的类型,也是 JavaScript 的核心机制之一。在 JavaScript 中,对象的定义是“属性的集合”。属性分为数据属性和访问器属性,二者都是 key-value 结构,key 可以是字符串或者 Symbol 类型。

说到对象,肯定会想起C++和Java中的类,每个类都是一个类型,但是Javascript中的"类"仅仅是运行时对象的一个私有属性,而 JavaScript 中是无法自定义类型的。

Javascript中的几个基本类型在对象中都一个对应,它们是:Number,String,Boolean,Symbol。注意1和new Number(1)是完全不同的值,一个是number类型一个是对象类型。Number、String 和 Boolean,三个构造器是两用的,当跟 new 搭配时,它们产生对象,当直接调用时,它们表示强制类型转换。Symbol 函数比较特殊,直接用 new 调用它会抛出错误,但它仍然是 Symbol 对象的构造器。

Javascript在设计上试图模糊对象和基本类型之间的关系:

<html>

<head>Symbol value</head>

<body>
    <script type="text/javascript">
        console.log("abc".charAt(0)); //a
        Symbol.prototype.hello = () => console.log("hello");
        var a = Symbol("a"); 
        console.log(typeof a); //symbol,a并非对象
        a.hello(); //hello,有效
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 判断类型

有两个用于判断变量类型的操作符typeof和instanceof。

# typeof

typeof操作符可能会有下列结果:

  • undefined——表示值未定义
  • boolean——表示值是布尔值
  • string——表示值是字符
  • number——表示值是数值
  • object——表示值是对象或null
  • function——表示值是函数

typeof

# instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链,它的语法为:

object instanceof constructors

下面例子的出处是instanceof (opens new window)

<html>

<head>instanceof</head>

<body>
    <script type="text/javascript">
        // 定义构造函数
        function C() { }
        function D() { }

        var o = new C();


        console.log(o instanceof C); // true,因为 Object.getPrototypeOf(o) === C.prototype
        console.log(o instanceof D); // false,因为 D.prototype 不在 o 的原型链上
        console.log(o instanceof Object); // true,因为 Object.prototype.isPrototypeOf(o) 返回 true
        C.prototype instanceof Object // true,同上
        C.prototype = {};

        console.log("--------------");
        var o2 = new C();
        console.log(o2 instanceof C); // true
        console.log(o instanceof C); // false,C.prototype 指向了一个空对象,这个空对象不在 o 的原型链上.

        console.log("--------------");
        D.prototype = new C(); // 继承
        var o3 = new D();
        console.log(o3 instanceof D); // true
        console.log(o3 instanceof C); // true 因为 C.prototype 现在在 o3 的原型链上
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

需要注意的是,如果表达式 obj instanceof Foo 返回 true,则并不意味着该表达式会永远返回 true,因为 Foo.prototype 属性的值有可能会改变,改变之后的值很有可能不存在于 obj 的原型链上,这时原表达式的值就会成为 false。另外一种情况下,原表达式的值也会改变,就是改变对象 obj 的原型链的情况,虽然在目前的ES规范中,我们只能读取对象的原型而不能改变它,但借助于非标准的 proto 伪属性,是可以实现的。比如执行 obj.proto = {} 之后,obj instanceof Foo 就会返回 false 了。

# 运算符

  1. 一元运算符:只有一个运算数的运算符++,--,+(正号),-(负号)。
  2. 算数运算符:+ - * / %
  3. 赋值运算符:= += -=
  4. 比较运算符:> < >= <= == ===(全等于)。它们的比较方式如下:
    • ==比较:
      1. 类型相同:直接比较,如果是字符串,按位逐一比较,直到得出大小为止(按照字典顺序算大小)。
      2. 类型不同:先进行类型转换,再比较。
    • ===全等比较:
      • 在比较之前,先判断类型,如果类型不一样,则直接返回false
  5. 逻辑运算符:&& || !
  6. 三元运算符:表达式? 值1:值2。判断表达式的值,如果是true则取值1,如果是false则取值2。

# 类型转换

在JS中,如果运算数不是运算符所要求的类型,那么js引擎会自动将运算数进行类型转换。各个类型转换的规则如下:

typeconvert

# StringToNumber

<html>

<head>StringToNumber</head>

<body>
    <script type="text/javascript">
       console.log(new Number("0b111"));
       console.log(parseInt("111",2));
       console.log(new Number("0o13"));
       console.log(parseInt("13",8));
       console.log(new Number("0xFF"));
       console.log(parseInt("FF",16));
       console.log(new Number("-1e-3"));
       console.log(parseFloat("-1e-3"));
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

多数情况下,Number 是比 parseInt 和 parseFloat 更好的选择。

# NumberToString

<html>

<head>NumberToString</head>

<body>
    <script type="text/javascript">
        var num=23;
        console.log(num.toString(2))
        console.log(num.toString(8))
        console.log(num.toString(10))
        console.log(num.toString(16))
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 装箱转换

上面提到number,string,boolean,symbol在对象中都有对应的类,所谓装箱是把基本类型转换为对应的对象。

<html>

<head></head>

<body>
    <script type="text/javascript">
        var symbolObject = (function(){ return this; }).call(Symbol("a")); 
        console.log(typeof symbolObject); //object 
        console.log(symbolObject instanceof Symbol); //true 
        console.log(symbolObject.constructor == Symbol); //true
        console.log("-------------------------")

        var symbolObject = Object(Symbol("a")); 
        console.log(typeof symbolObject); //object 
        console.log(symbolObject instanceof Symbol); //true 
        console.log(symbolObject.constructor == Symbol); //true
        console.log("-------------------------")

        var symbolObject = Object(Symbol("a")); 
        console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]
    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

注意:

  1. 每一类装箱对象皆有私有的 Class 属性,这些属性可以用 Object.prototype.toString 获取,在 JavaScript 中,没有任何方法可以更改私有的 Class 属性,因此 Object.prototype.toString 是可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。
  2. call 本身会产生装箱操作,所以需要配合 typeof 来区分基本类型还是对象类型。
  3. 装箱机制会频繁产生临时对象,在一些对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。

# 拆箱转换

在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换,也就是拆箱转换。

对象到 String 和 Number 的转换都遵循"先拆箱再转换"的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。

<html>

<head></head>

<body>
    <script type="text/javascript">
        var o = { 
            valueOf : () => {console.log("valueOf"); return {}}, 
            toString : () => {console.log("toString"); return "10"} 
        } 
        console.log(o * 2)
        console.log("----------")
        console.log(typeof(o*2))
        console.log("--------------------")
        var o2 = { 
            valueOf : () => {console.log("valueOf"); return 20}, 
            toString : () => {console.log("toString"); return {}} 
        } 
        console.log(String(o))
        console.log("----------")
        console.log(String(o2))
        console.log("--------------------")
        o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"} 
        console.log(String(o))

    </script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

注意,在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。