zoukankan      html  css  js  c++  java
  • JavaScript学习(1)之JavaScript基础

    JavaScript学习(1)之JavaScript基础

    由于工作原因,开发语言逐渐以JavaScript为主,所以,抽空学习了下JavaScript语法。等现阶段的工作稳定之后,陆续会分享下自己在学习和开发过程中的一些经验总结。本着"技多不压身"的原则以及对各种编程语言的热爱,虽然笔者一直从事游戏开发工作,也愉快而又义无反顾的加入了JavaScript阵营。

    1、JavaScript概述以应用范围

    1.1 JavaScript概述

    首先,JavaScript是一门动态类型的编程语言。支持面向对象、函数式等编程范式。同时,它也是运行在宿主环境下的轻量级的脚本语言,例如:浏览器,JavaScript代码可嵌入到HTML页面中。当然,也有应用基于Node.js的服务器环境。可以说它是主流浏览器都支持的脚本语言,这也造就了JavaScript在PC、手机、平板等环境处理与网页交互时的天然优势。随着HTML5的推广与广泛应用,出现了大量基于JavaScript的跨平台框架和游戏引擎,是通往全栈开发很值得学习的一门编程语言。正如在编程语言界流行着“世界终将是JS的”的槽点,足以可见JavaScript的强大。

    1.2 JavaScript应用范围

    web前端:最近比较火的Vue.js、React、 Angular等等前端框架层出不穷。
    手机app:React Native、PhoneGap、Weex等跨平台移动框架等,以及微信小程序等。
    游戏引擎:Cocos2d-js、Unity、egret等引擎都支持JavaScript脚本语言。
    服务器:pomelo、Node.js等。

    2、基本概念

    2.1 语法

    JavaScript语法中大量借鉴了C和Java、Perl等语言的语法,因此熟悉这些语言的人再来学习JavaScript会感觉似曾相识。但JavaScript与Java是两回事,JavaScript完全是蹭了当年Java的热度,来推动自己。

    一个完整的JavaScript实现由下面三个不同的部分组成:

    • 核心(ECMAScript)
    • 文档对象模型(DOM)
    • 浏览器对象模型(BOM)

    ECMAScript:
    定义了语言的基础,规定并对该语言在语法、类型、语句、关键字、保留字、操作符、对象等方面作了具体描述,并不包含输入和输出。

    DOM:
    文档对象模型(Document Object Model)是XML和HTML的编程接口。DOM会把整个页面映射为一个多层节点结构树,程序可以对结构树进行添加、删除、替换或修改任何节点。

    BOM:
    浏览器对象模型(Browser Object Model)支持访问和操作浏览器窗口。

    注:以下描述就不区分ECMAScript和JavaScript,也不简称js,均统称JavaScript。

    2.2 标识符

    这里的标识符,统指变量名、函数名、属性名以及函数参数的名字。那么,标识符的命名有以下规则:(这些规则跟C、Java等语言类似)

    • 第一个字符必须是一个字母、下划线_或者美元符号$
    • 其他字符则可以是字母、数字、下划线和美元符号。
    • 不能使用关键字、保留字来定义标识符。例如:if、for等。

    注:在JavaScript中,变量名、函数名甚至操作符等是区分大小写的。即name和Name代表的是两个不同的变量。

    2.3 注释

    单行注释使用//。例如:

    // 这是一个单行注释
    

    多行注释使用/***/结构。例如:

    /*
     * 这是一个多行注释
     */
    

    2.4 严格模式

    启用严格模式,会对一些不确定的行为或不安全的操作抛出错误。在脚本的顶部添加如下字符串:

    "use strict";
    

    也可以添加到函数内部,来指定该函数在严格模式下执行。

    2.5 语句

    JavaScript中每条语句都是以分号结尾,但分号不是必需的。但建议是不要省略分号,来避免如不完整的输入等问题。语句块用花括号包含。

    2.6 变量

    可以使用var关键字来定义变量,JavaScript的变量可以用来保存任何类型的数据。例如:

    var name = "AlphaGL";
        name = 18;
        name = function() {
        };
    

    也可以使用逗号来定义多个变量。例如:

    var name = "AlphaGL",
        age = 18,
        work = true;
    

    虽然JavaScript支持这种写法,一般不推荐。不仅可读性差,而且易出错。

    在函数内声明的变量,那么该变量的作用域为该函数内。例如:

    function test() {
        var name = "AlphaGL";
    }
    
    test();
    console.log(name); // ReferenceError: name is not defined
    

    虽然可以去掉var声明来解决这个问题,例如:

    function test() {
        name = "AlphaGL";
    }
    
    test();
    console.log(name);
    

    这时,name就成了全局变量。一般不推荐这种做法。全局变量难以维护,而且容易导致结构混乱。

    3、数据类型

    JavaScript中数据类型主要有:Number、String、Boolean、Object、undefined、null这几种类型。以及涉及到类型判断时会用到的操作有:typeof、instanceof、Object.prototype.toString。

    3.1 Number

    (1)JavaScript中并不区分整型或浮点数,所有数字均统一采用64位浮点数表示。可用Number.MIN_VALUE和Number.MAX_VALUE来获取Number类型的值的范围。

    console.log(Number.MIN_VALUE); // 5e-324
    console.log(Number.MAX_VALUE); // 1.7976931348623157e+308
    

    (2)NaN
    非数值(Not a Number)是一个特殊的数值。当算术运算返回一个未定义或无法表示的值时,就产生NaN了。

    console.log(typeof(NaN));   // number
    console.log(0 / 0);         // NaN
    console.log("hello" / 5);   // NaN
    console.log(NaN + 1);       // NaN
    

    注:0除以0会返回NaN。其它数除以0不会。

    判断一个变量是否是非数值,可以使用isNaN()函数。

    console.log(isNaN(NaN));        // true
    console.log(NaN == NaN);        // false
    console.log(isNaN("hello"));    // true,"hello"不能转换成数值
    console.log(isNaN(true));       // false
    console.log(isNaN(false));      // false
    console.log(isNaN(5));          // false
    console.log(isNaN("5"));        // false
    

    注:NaN与其它所有值都不相等,包括它自己。因此判断NaN不要用=====

    (3)Infinity(正无穷)和-Infinity(负无穷)
    JavaScript中还有两个特殊的数值Infinity和-Infinity。

    console.log(typeof(Infinity));  // number
    console.log(typeof(-Infinity)); // number
    console.log(1 / 0);             // Infinity
    console.log(1 / -0);            // -Infinity
    

    判断一个变量是否是有穷数,可以使用isFinite()函数。

    console.log(isFinite(Infinity));  // false
    console.log(isFinite(-Infinity)); // false
    console.log(isFinite(NaN));       // false
    console.log(isFinite(0));         // true
    console.log(isFinite("0"));       // true
    

    3.2 String

    (1)String类型字符串是由单引号或双引号包括的零个或多个字符序列。

    var key = "hello";
    var value = 'world';
    

    (2)字符串可以像数组一样访问,但一旦字符串的值确定了,就不能改变。

    var foo = "Java";
    foo[0] = "S";
    console.log(foo); // Java
    
    foo = foo + "Script";
    console.log(foo); // JavaScript
    

    (3)toString()与String

    var a = 5;
    var b = false;
    var c = "AlphaGL"
    var d = new Date();
    
    console.log(a); // 5
    console.log(b); // false
    console.log(c); // AlphaGL
    console.log(d); // 2017-05-12T05:54:30.547Z
    
    console.log(String(5));         // 5
    console.log(String(false));     // false
    console.log(null);              // null
    console.log(undefined);         // undefined
    

    还有一些常规的String操作的api,可以查阅JavaScript的String api文档。

    3.3 Boolean

    该类型只有两个字面值:true和false。需要强调的是,true不一定是1,false也不一定是0。其它类型转换成Boolean类型时,有一下规则:

    • Boolean
      true转换成true,false转换成false。
    • String
      任何非空字符串转换成true,空字符串("")转换成false。
    • Number
      任何非零数字值(包括无穷大)转换成true,0和NaN转换成false。
    • Object
      任何对象转换成true,null转换成false。
    • Undefined
      undefined转换成false。
    console.log(Boolean("AlphaGL"));    // true
    console.log(Boolean("false"));      // true
    console.log(Boolean(""))            // false
    console.log(Boolean(10));           // true
    console.log(Boolean(Infinity));     // true
    console.log(Boolean(-Infinity));    // true
    console.log(Boolean(0));            // false
    console.log(Boolean(NaN));          // false
    console.log(Boolean(new Date()));   // true
    console.log(Boolean(null));         // false
    console.log(Boolean(undefined));    // false
    

    综上:当值为0,null,false,NaN,undefined,空字符串("")转换成Boolean类型时值都为false,其它任何值都为true。

    以下实例:
    (1)

    var x;
    if (x) {
        console.log("test1")
    }else {
        console.log("test2")
    }
    

    输出:test2

    (2)

    var x = null;
    if (x) {
        console.log("test1")
    }else {
        console.log("test2")
    }
    

    输出:test2

    (3)

    var x = Boolean(false);
    if (x) {
        console.log("test1")
    }else {
        console.log("test2")
    }
    

    输出:test2

    (4)

    var x = false;
    if (x) {
        console.log("test1")
    }else {
        console.log("test2")
    }
    

    输出:test2

    (5)

    var x = new Boolean(false);
    if (x) {
        console.log("test1")
    }else {
        console.log("test2")
    }
    

    输出:test1

    综上:任何值不为undefined或者null的对象, 包括值为false的Boolean对象, 在条件语句中,其值都将作为true来判断。

    3.4 Object

    对象是JavaScript中重要的数据类型,除数字、true、false、null、undefined和字符串外,所有的值都是对象。在JavaScript中对象是一组无序的键值对集合,类似其它语言的HashMap、Dictionary等数据结构。下面会有单独一小节来专门讲述下对象。

    3.5 undefined

    在使用var声明变量但未对其初始化时,该变量的值即为:undefined。undefined类型也只有这一个值。

    字面值undefined主要用于比较。

    var i = undefined;
    console.log(i == undefined); // true
    

    未初始化的变量与未声明的变量

    var foo;
    console.log(foo); // undefined
    console.log(bar); // 报错
    
    var foo;
    console.log(typeof(foo)); // undefined
    console.log(typeof(bar)); // undefined
    

    使用typeof操作符来检测未初始化的变量与未声明的变量,都返回undefined。

    3.6 null

    与undefined类似,null类型也只有一个值null。空值null表示一个空的对象指针。

    console.log(typeof(null)); // object
    

    null与undefined比较:

    console.log(Number(null));          // 0
    console.log(Number(undefined));     // NaN
    
    console.log(isNaN(1 + null));       // false
    console.log(isNaN(1 + undefined));  // true
    
    console.log(null === undefined);    // false
    console.log(null == undefined);     // true
    

    3.7 typeof、instanceof与Object.prototype.toString

    由于JavaScript是动态类型的,因此就需要一种手段来检测给定的变量的具体数据类型。

    (1)typeof
    typeof操作符返回的值是以下某个字符串:

    • "undefined"
    • "boolean"
    • "string"
    • "number"
    • "object"
    • "function"
    console.log(typeof(123));           // number
    console.log(typeof("123"));         // string
    console.log(typeof(NaN));           // number
    console.log(typeof(Infinity));      // number
    console.log(typeof(false));         // boolean
    console.log(typeof(null));          // object
    console.log(typeof([]));            // object
    console.log(typeof({}));            // object
    console.log(typeof(function(){}));  // function
    console.log(typeof(undefined));     // undefined
    

    (2)instanceof:下文会讨论。
    (3)Object.prototype.toString:下文会讨论。

    4、流程控制与运算符

    4.1 流程控制

    流程控制主要有条件语句和循环语句。条件语句又分为:if语句,if-else语句,switch语句等。循环语句分为:while循环,for循环,do-while循环。

    4.1.1 条件语句

    (1)if语句

    if (condition1) {
        statement1;
    }else if(condition2) {
        statement2;
    }else {
        statement3;
    }
    

    该结构,当条件1(condition1为true)满足时,执行语句1(statement1),否则条件2(condition2为true)满足时,执行语句2,以上都不满足则执行语句3(statement3)。

    这里的else if可以有零个或多个,甚至else都是可选的,根据实际情况来做实际判断。

    if (i > 0) {
        console.log("i大于0");
    }else if(i < 0) {
        console.log("i小于0");
    }else {
        console.log("i等于0");
    }
    

    (2)switch语句
    switch是一种多分支结构,与if结构类似,也是一种普遍使用的流程控制语句。

    switch(expression) {
        case value1:
            statement1;
            break;
        case value2:
            statement2;
            break;
        default:
            statement3;
    }
    

    当表达式(expression)的值,等于对应case的值(value),则会执行对应的语句块(statement),break用于跳出该switch结构,若省略break,则会继续执行下一个case。default用于以上表达式的值都不满足条件。

    表达式(expression)可以使用任何数据类型,包括字符串、对象。case的值(value)也可以是变量、表达式,不一定是常量。这点与其他有的语言不同,只能使用数值。

    4.1.2 循环语句

    (1)while语句
    while循环包含循环条件和循环体,当循环条件满足条件时,才会执行循环体内的代码块。

    while(expression) {
        statement;
    }
    
    while(i < 100) {
        i++;
    }
    

    (2)for语句
    for语句一般包含初始条件、循环条件、循环递增(递减)语句三部分。

    for(initialization; expression; post-loop-expression) {
        statement;
    }
    
    for(var i = 0; i < 100; i++) {
        console.log(i);
    }
    

    (3)for-in语句
    该语句类似于其他语言的foreach语句,可以用了遍历容器结构或对象的属性。

    for (property in expression) {
        statement;
    }
    
    var o = [1, 2, 3];
    for(var i in o) {
        console.log(o[i]);
    }
    

    (4)do-while语句
    do-while语句与while语句类似,不同的时,do-while语句会先执行循环体,再检测循环条件,这就意味着该循环语句至少会执行一次。

    do {
        statement;
    }while(expression);
    

    (5)break与continue
    break和continue都可用于控制循环中代码的执行。
    break:立即退出循环。
    continue:跳出当前循环,继续执行下次循环。

    4.2 运算符

    JavaScript中运算符大体可分为,算术运算符、关系运算符、逻辑运算符以、位运算符以及其它操作符等,当然也包括运算符重载。

    • 算术运算符:+-*/%++--+=-=*=/=%=等。
    • 关系运算符:<><=>======!=!==
    • 逻辑运算符:&&||!
    • 位运算符:~&|^<<>>>>>
    • 其他操作符:=,?=
    4.2.1 算术运算符

    (1)加法操作符(+)
    加减法操作符时JavaScript中最常用得操作符之一,与其他语言不同得是,在JavaScript中有一系列特殊得行为,使用时应当注意。

    console.log(1 + 2);                      // 3
    console.log(1 + "2");                    // 12
    console.log("1" + "2");                  // 12
    console.log(1 + [2]);                    // 12
    console.log(1 + "2" + 3);                // 123
    console.log("1" + 2 + 3);                // 123
    console.log(1 + 2 + "3");                // 33
    console.log("1" + 2 + "3");              // 123
    console.log(1 + [2, 3]);                 // 12,3
    console.log(1 + "");                     // 1
    console.log(0.1 + 0.2);                  // 0.30000000000000004
    console.log(0.05 + 0.25);                // 0.3
    
    // boolean
    console.log(1 + true);                   // 2
    console.log(1 + false);                  // 1
    
    // string
    console.log("AlphaGL:" + null);          // AlphaGL:null
    console.log("AlphaGL:" + undefined);     // AlphaGL:undefined
    console.log("AlphaGL:" + Infinity);      // AlphaGL:Infinity
    console.log("AlphaGL:" + NaN);           // AlphaGL:NaN
    console.log("AlphaGL:" + false);         // AlphaGL:false
    console.log("AlphaGL:" + true);          // AlphaGL:true
    
    // infinity
    console.log(1 + Infinity);               // Infinity
    console.log(Infinity + Infinity);        // Infinity
    console.log(1 + (-Infinity));            // -Infinity
    console.log((-Infinity) + (-Infinity));  // -Infinity
    console.log(Infinity + (-Infinity));     // NaN
    
    // 0
    console.log((+0) + (+0));                // 0
    console.log((-0) + (-0));                // -0
    console.log(0 + (-0));                   // 0
    
    // NaN
    console.log(1 + NaN);                    // NaN
    
    console.log(1 + null);                   // 1
    console.log(1 + undefined);              // NaN
    console.log(1 + (new Date()));           // 1Mon May 25 2017 17:09:08 GMT+0800 (中国标准时间)
    console.log(1 + {name: "AlphaGL"});      // 1[object Object]
    

    综上,使用加法操作符可以总结为如下规则:

    • 如果两个操作数都是数值,则执行常规得加法计算。这里需要注意浮点数的加法。
    • 如果一个操作数为字符串,则将另外一个操作数也转化成字符串类型,再执行字符串的拼接。
    • 如果一个操作数是数值,另外一个操作是Infinity,则加法结果为Infinity。如果一个操作数是数值,另外一个操作数是-Infinity,则加法结果为-Infinity。如果是Infinity加-Infinity,则加法结果为NaN。如果一个操作数是数值,另外一个操作数是NaN,则加法结果为NaN。
    • 如果一个操作数是数值,另外一个操作数是boolean,null类型,则先将boolean和null类型转行成原始值,再执行加法运算。
    • 如果一个操作数是数值,另外一个操作数是对象,则会先调用对象的valueOf方法转化成原始值,如果对象没有valueOf方法,则调用toString方法。

    (2)减法运算符(-)
    减法的运算规则与加法类似,这里就不再详细介绍了。

    (3)乘法运算符(*)

    console.log(2 * 3);                  // 6
    console.log(-2 * -3);                // 6
    console.log(2 * -3);                 // -6
    console.log(2 * Number.MAX_VALUE);         // Infinity
    console.log(-2 * Number.MAX_VALUE);        // -Infinity   
    // NaN
    console.log(2 * NaN);                // NaN
    console.log(-2 * NaN);               // NaN
    console.log(0 * NaN);                // NaN
    console.log(NaN * NaN);              // NaN
    // Infinity
    console.log(2 * Infinity);           // Infinity
    console.log(-2 * Infinity);          // -Infinity
    console.log(-2 * -Infinity);         // Infinity
    console.log(0 * Infinity);           // NaN
    console.log(Infinity * Infinity);    // Infinity
    console.log(-Infinity * -Infinity);  // Infinity
    // undefined
    console.log(2 * undefined);          // NaN
    console.log(0 * undefined);          // NaN
    console.log(undefined * undefined);  // NaN
    // boolean
    console.log(2 * false);              // 0
    console.log(2 * true);               // 2
    
    console.log(2 * "34");               // 68
    console.log(2 * "AlphaGL");          // NaN
    console.log(2 * [3, 4]);             // NaN
    console.log(2 * {name:"34"});        // NaN
    console.log(2 * new Date());         // 2992421935692
    

    综上,使用减法操作符可以总结为如下规则:

    • 两个正数或两个负数相乘,结果为正数。其它有一个操作数为负数,那结果也为负数。如果结果超出数值的表示范围,则结果为Infinity或-Infinity。
    • 如果有一个操作数为NaN或undefined,则结果为NaN。
    • 如果一个非0数值与Infinity或-Infinity相乘,则结果为Infinity或-Infinity,符号取决于操作数的符号和Infinity还是-Infinity。0与Infinity或-Infinity,则结果为NaN。
    • 如果一个操作数是数值,另外一个操作数是boolean或者字符串,则先将该操作数转化为原始值,如果转化后的值不是数值,则结果为NaN,否则执行常规乘法运算。
    • 如果一个操作数是数值,另外一个操作数是对象,则结果为NaN。如果是Date对象,则乘以基于当前到1970年1月1日起的毫米数。

    (4)除法操作数(/)
    除法的运算规则与乘法类似,同样,这里就不再详细介绍了。

    (5)模(求余)运算(%)
    该运算符是求得除法运算后的余数。

    console.log(10 % 3);     // 1
    console.log(-10 % 3);    // -1
    console.log(10 % -3);    // 1
    
    console.log(10 % 3.14);  // 0.5799999999999996
    

    综上,模运算规则如下:

    • 模运算的结果的符号,与第一个操作数相同。模运算用于浮点数时,结果会有误差。

    (6)自增(++)与自减(--)
    自增和自减有分为前置和后置。

    var x = 5;
    var y = ++x - 2
    /* 等价于
    * var x = 5;
    * x = x + 1;
    * var y = x - 2;
    */
    
    console.log(x); // 6
    console.log(y); // 4
    
    var x = 5;
    var y = x++ - 2;
    /* 等价于
    * var x = 5;
    * var y = x - 2;
    * x = x + 1;
    */
    
    console.log(x); // 6
    console.log(y); // 3
    

    前置自增与后置自增的区别是,前置自增先执行自增,再执行后续的运算,后置自增是先执行运算,再执行自增。同理,自减原理也一样,就不在赘述了。

    (7)x op= y操作
    这里把+=-=*=/=%=等复合运算统称为op=,那么:

    x op= y
    

    大多数情况下等价于:

     x = x op y
    

    其中,下面这个表达式中x计算了两次,在x含有副作用的表达式时,二者就不等价了。

    var c = [1, 2, 3];
    var i = 0;
    c[i++] *= 2;
    console.log(c)
    // [ 2, 2, 3 ]
    
    var d = [1, 2, 3];
    var j = 0;
    d[j++] = d[j++] * 2;
    console.log(d);
    // [ 4, 2, 3 ]
    
    4.2.2 关系运算符

    用来判断一个操作数是否大于或小于或等于另外一个操作数。

    console.log(2 < 3);                     // true
    console.log("12" < 3);                  // false
    console.log("12" < "3");                // true               
    console.log("Alpha" < "alpha");         // true
    console.log("AlphaGL" < "AlphagL");     // true
    console.log(2 < "AlphaGL");             // false
    console.log(2 < true);                  // false
    console.log(2 < undefined);             // false
    console.log(2 < null);                  // false
    console.log(2 < NaN);                   // false
    console.log(false < true);              // true
    console.log(2 < Infinity);              // true              
    console.log(2 < -Infinity);             // false
    console.log(Infinity < Infinity);       // false
    console.log(Infinity < Infinity + 1);   // false
    console.log(0 <= 0);                    // true
    console.log(0 >= 0);                    // true
    console.log(12 == "12");                // true
    console.log(12 === "12");               // false
    console.log(12 !== "12");               // true
    console.log(undefined == 0);            // false
    console.log(undefined == null);         // true
    console.log(undefined == false);        // false
    console.log(null == false);             // false
    console.log(null == 0);                 // false
    console.log("" == 0);                   // true
    console.log(undefined == "");           // false
    console.log(2 != NaN);                  // true
    console.log(NaN == NaN);                // false
    console.log(NaN != NaN);                // true
    console.log(false == 0);                // true
    console.log(true == 1);                 // true
    

    综上,关系运算符返回的都是boolean值,有以下规则:

    • 如果比较的两个操作数都是数值,则执行数值比较。如果只有一个操作数是数值,则将另外一个操作数转换为数值,再执行数值比较。
    • 如果比较的两个操作数都是字符串,则依次比较字符串每个字符的Unicode值。
    • 如果有一个操作数是NaN,则执行结果为false,执行不相等操作时,执行结果为true。
    • null和undefined相等。但不能将null和undefined转化为其它任何值。
    • 如果有一个操作数是对象,另外一个操作数不是,则会调用对象的valueOf方法得到原始值,再应用上面的规则。
    • 当两个操作数的值相同,类型也相同,并且都不是NaN时,则两个操作数全等(=)。当比较的两个操作数转换为相同类型后的值相等,则两个操作数相等()。
    4.2.3 逻辑运算符

    (1)逻辑与(&&)
    在boolean环境下当逻辑与的两个操作数同时为true时,结果才为true,否则为false。

    console.log(new Date() && 2);               // 2
    console.log(2 && new Date());               // 2017-05-31T02:39:51.033Z
    console.log(false && new Date());           // false
    console.log(new Date() && new Date());      // 2017-05-31T02:39:51.035Z
    console.log(false && 0);                    // false
    console.log(true && 0);                     // 0
    console.log(2 && 0);                        // 0
    console.log(2 && "");                       // ""
    console.log(2 && "AlphaGL");                // AlphaGL
    console.log(2 && null);                     // null
    console.log(2 && undefined);                // undefined
    console.log(2 && NaN);                      // NaN
    console.log(2 && Infinity);                 // Infinity
    

    综上,逻辑与的使用规则可以总结如下:

    • 如果第一个操作数能转换成false,则返回第一个操作数,否则返回第二个操作数。在boolean环境中使用时,两个操作数结果都为true时,返回true,否则返回false。
    • 能够转换为false的值有,0,"",null,undefined。

    短路操作:
    在执行逻辑与操作时,当第一个操作数的结果为false时,就不在执行第二个操作数的求值了。因为无论第二个操作数为何值,其结果都不可能为true。

    function test(i) {
        if(i > 0) {
            return i;
        }else{
            return -i;
        }
    }
    
    console.log(false && test(2));      // false
    console.log(true && test(2));       // 2
    

    (2)逻辑或(||)
    在boolean环境下当逻辑或的两个操作数任意一个为true时,结果都为true。一般,可用来给变量设置默认值。

    console.log(new Date() || 2);               // 2017-05-31T02:46:51.732Z
    console.log(2 || new Date());               // 2
    console.log(false || new Date());           // 2017-05-31T02:48:51.732Z
    console.log(new Date() || new Date());      // 2017-05-31T02:48:51.732Z
    console.log(false || 0);                    // 0
    console.log(true || 0);                     // true
    console.log(2 || 0);                        // 2
    console.log(2 || "");                       // 2
    console.log(2 || "AlphaGL");                // 2
    console.log(2 || null);                     // 2
    console.log(2 || undefined);                // 2
    console.log(2 || NaN);                      // 2
    console.log(2 || Infinity);                 // 2
    

    综上,逻辑或的使用规则可以总结如下:

    • 如果第一个操作数能转换成true,则返回第一个操作数,否则返回第二个操作数。在boolean环境中使用时,两个操作数任意一个为true时,返回true,否则返回false。
    • 能够转换为false的值有,0,"",null,undefined。

    短路操作:
    在执行逻辑或操作时,当第一个操作数的结果为true时,就不在执行第二个操作数的求值了。因为无论第二个操作数为何值,其结果都不可能为false。

    function test(i) {
        if(i > 0) {
            return i;
        }else{
            return -i;
        }
    }
    
    console.log(false || test(2));      // 2
    console.log(true || test(2));       // true
    

    (3)逻辑非(!)
    无论操作数是什么类型的数据,该操作都会返回一个boolean。逻辑非会先将操作数转换为一个boolean,再对齐求反。

    console.log(!0);            // true
    console.log(!"");           // true
    console.log(!NaN);          // true
    console.log(!null);         // true
    console.log(!undefined);    // true   
    console.log(!Infinity);     // false
    console.log(!2);            // false
    console.log(!"AlphaGL");    // false   
    console.log(!new Date());   // false
    

    综上,逻辑非的使用规则可以总结如下:

    • 如果操作数能转换为true的话,则返回false,否则返回false。
    • 能够转换为false的值有,0,"",null,undefined。
    4.2.4 位运算符

    位运算是比较低层次的运算,按内存中表示数值的位来操作数值。JavaScript中所有的数值都是以64位格式存储,而位操作符是先将64位的值转换成32位的整数,然后执行操作,最后再将结果转换回64位。

    对于有符号的整数,32中的前31位表示整数的值,第32位表示数值的符号,用0表示整数,1表示负数,因此第32位也叫符号位。其中,正数是以二进制格式存储的,负数二进制补码的形式存储的。

    (1)原码、反码和补码
    原码,是该数值的符号位与绝对值的二进制表示。例如:

    2[原码]:  0000 0000 0000 0000 0000 0000 0000 0010
    -2[原码]: 1000 0000 0000 0000 0000 0000 0000 0010
    

    反码,正数的反码是其原码。负数的反码,是符号位不变,其余各位取反,即1变成0,0变成1。例如:

    2[反码]: 0000 0000 0000 0000 0000 0000 0000 0010
    -2[反码]:1111 1111 1111 1111 1111 1111 1111 1101
    

    补码,正数的补码是其原码。负数的补码,是其反码加1。例如:

    2[补码]: 0000 0000 0000 0000 0000 0000 0000 0010
    -2[补码]:1111 1111 1111 1111 1111 1111 1111 1110
    

    (2)按位与(&)
    按位于,是将两个操作数的二进制位对齐,当两个数值的位都为1时,结果为1,任意一个为0,则结果为0。

    console.log(3 & 5); // 1
    
    3 = 0000 0000 0000 0000 0000 0000 0000 0011
    5 = 0000 0000 0000 0000 0000 0000 0000 0101
    & = 0000 0000 0000 0000 0000 0000 0000 0001
    

    (3)按位或(|)
    按位或,是将两个操作数的二进制位对齐,当两个数值的位任意一个为1时,结果为1,两个都为0,则结果为0。

    console.log(3 | 5); // 7
    
    3 = 0000 0000 0000 0000 0000 0000 0000 0011
    5 = 0000 0000 0000 0000 0000 0000 0000 0101
    | = 0000 0000 0000 0000 0000 0000 0000 0111
    

    (4)按位非(~)
    按位非,是得到该数值的反码。

    console.log(~3); // -4
    
    3 = 0000 0000 0000 0000 0000 0000 0000 0011
    ~ = 1111 1111 1111 1111 1111 1111 1111 1100
    

    (5)按位异或(^)
    按位异或,是将两个操作数的二进制位对齐,当两个数值的位其中只有一个为1时,结果为1,两个都为0或都为1,则结果为0。

    console.log(3 ^ 5); // 6
    
    3 = 0000 0000 0000 0000 0000 0000 0000 0011
    5 = 0000 0000 0000 0000 0000 0000 0000 0101
    ^ = 0000 0000 0000 0000 0000 0000 0000 0110
    

    (6)左移(<<)
    左移,是将操作数的所有位移动指定的位数,右侧多出的位用0填充。左移不影响操作数的符号位。

    console.log(3 << 2);    // 12
    console.log(-3 << 2);   // -12
    
    3    = 0000 0000 0000 0000 0000 0000 0000 0011
    << 2 = 0000 0000 0000 0000 0000 0000 0000 1100
    

    (7)有符号右移(>>)
    有符号右移,是将操作数的所有位移动指定的位数,并保留符号位。左侧多出的位用0填充。

    console.log(12 >> 2);   // 3
    console.log(-12 >> 2);  // -3
    
    12   = 0000 0000 0000 0000 0000 0000 0000 1100
    >> 2 = 0000 0000 0000 0000 0000 0000 0000 0011
    

    (8)无符号右移(>>>)
    无符号右移,是将操作数的所有位移动指定的位数。对于正数,无符号右移与有符号右移结果相同,负数会以补码的形式右移指定的位。

    console.log(12 >>> 2);   // 3
    console.log(-12 >>> 2);  // 1073741821
    
    4.2.5 其它运算符

    (1)赋值运算符(=)
    赋值可以和其他运算符组合使用。例如:

    var x = 3;
    console.log(x += 5); // 8
    

    (2)逗号运算符(,)
    逗号运算符,可以再一条语句中执行多个操作。如果,逗号运算符用于赋值,则返回表达式中的最后一项。

    var x = 2, y = 3, z = 5;
    
    var pos = (2, 3, 5);
    console.log(z);     // 5
    console.log(pos);   // 5
    

    (3)三目运算符(?=)
    三目运算符,格式形如:
    variable = boolean_expression ? true_value : false_value

    当表达式boolean_expression的值位true时,则返回true_value的值,否则,返回false_value的值。

    console.log(1 > 2 ? 1 + 2 : 1 - 2); // -1
    

    5、对象

    在介绍数据类型的时候提到过,在JavaScript中对象是一组无序的键值对集合,类似其它语言的HashMap、Dictionary等数据结构。除数字、true、false、null、undefined和字符串外,所有的值都是对象。JavaScript内置了Object、Date、Array、Function、RegExp等对象。所有对象继承Object对象。

    5.1 对象的创建

    对象的创建分为两种方式:
    (1)使用new操作符,后面紧跟构造函数

    var student = new Object(); // 等价于 var student = {};
    student.name = "AlphaGL";
    student.age = 18;
    student.print = function () {
        console.log("hello AlphaGL");
    }
    

    (2)使用对象字面量表示法。由若干名/值对中间用冒号分割,每对名/值对间用逗号分隔组成的映射表。

    var student = {
        name : "AlphaGL",
        age  : 18
        print: function () {
            console.log("hello AlphaGL");
        },
    };
    

    5.2 读取属性

    可以通过点(.)或者中括号([])的方式获取对象属性的值。

    (1)通过点(.)来获取

    var student = {
        name : "AlphaGL",
        age  : 18
    };
    
    console.log("name = " + student.name); // name = AlphaGL
    

    (2)通过中括号访问属性的值,中括号内可以是变量且计算结果必须是字符串的表达式。如果属性名包含回导致语法错误的字符,或者属性名使用的是关键字或者保留字,也可以使用中括号表示。

    var name = "nick name";
    student[name] = "AlphaGL"; // 等价于 student["nick name"] = "AlphaGL";
    

    一般推荐使用点的方式去获取对象属性。

    5.3 检测属性

    (1)hasOwnProperty()方法可以检测给定属性存在于对象实例中时,则返回true。

    function Student() {
    
    }
    
    Student.prototype.work = "game";
    
    var stu = new Student();
    stu.name = "AlphaGL";
    stu.age  = 18;
    
    console.log(stu.hasOwnProperty("name")); // true
    console.log(stu.hasOwnProperty("work"))  // false
    

    (2)in操作符会访问对象的给定属性,无论该属性是存在于实例中还是原型中都返回true。

    function Student() {
    
    }
    
    Student.prototype.work = "game";
    
    var stu = new Student();
    stu.name = "AlphaGL";
    stu.age  = 18;
    
    console.log("name" in stu); // true
    console.log("work" in stu)  // true
    

    5.4 删除属性

    delete运算符可以用来删除对象的自有属性,不会删除原型的同名属性,删除不存在的属性在对象上,delete将不会起作用,但仍会返回true。成功删除的时候会返回true,否则返回false。

    function Student() {
        
    };
    
    Student.prototype.name = "hello";
    
    var stu = new Student();
    stu.name = "AlphaGL";
    stu.age = 18;
    
    console.log(delete stu.name); // true
    console.log(delete stu.name); // 什么不做,同样返回true
    console.log(stu.name);        // hello
    

    5.5 Array对象

    JavaScript中,数组算是最常用的类型。数组的大小可以动态调整,每一项可以保存任何类型的数据,起始项从0开始。还可以实现堆栈,队列等数据结构。

    (1)数组的创建

    • 使用Array构造函数创建。
        var nums = new Aarray(3);
        var names = new Array("foo", "bar")
        var colors = Array("R", "G", "B")
      
    • 使用数组字面量表示法。即使用中括号,并将每个元素用逗号隔开。
        var num = [1, 2, 3];
        var names = ["foo", "bar"];
        var params = [1.2, "ab", true];
        var pos = [{x:1, y:2}, {x:3, y:4}];
      

    (2)数组元素的访问。

    var a = ["AlphaGL", 18, true];
    console.log(a[0]);                  // AlphaGL
    console.log(a[1]);                  // 18
    console.log(a[2]);                  // true
    //indexOf返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。类似的还有lastIndexOf方法。
    console.log(a.indexOf("AlphaGL"));  // 0
    console.log(a.indexOf(true));       // 2
    console.log(a.indexOf(18));         // 1
    console.log(a.indexOf(2));          // -1
    
    console.log(a.length);              // 3
    console.log(a[3]);                  // undefined。javascript数组下标从0开始。
    

    可以使用负数或非整数来索引数组。这时,数值将会转换为字符串,而该索引被当作对象的常规属性。如果,使用了非负整数的字符串,则它会被当作数组索引访问,而不是对象属性访问。

    var a = ["AlphaGL", 18, true];
    console.log(a[-1]);     // undefined
    console.log(a[1.5]);    // undefined
    
    console.log(a["1"]);    // 18
    console.log(a["2"]);    // true
    

    由此可知,数组的访问只是对象访问的一种特殊形式,当访问不存在的属性时,javascript也不会报错,只会返回undefined值。因此,javascript中数组不存在数组越界的问题。

    (3)数组元素的添加与删除
    添加元素:

    var a = [];
    a[0] = "AlphaGL";
    a.push(18, true);
    
    console.log(a);     // [ 'AlphaGL', 18, true ]
    

    删除元素:

    var a = ["AlphaGL", 18, true];
    delete a[1];
    console.log(a[1]);      // undefined
    console.log(a.length);  // 3
    console.log(a.pop());   // true。从数组中删除最后一个元素,并返回该元素的值
    console.log(a.length);  // 2
    console.log(a.shift())  // AlphaGL。从数组中删除第一个元素,并返回该元素的值
    console.log(a.length);  // 1
    a[0] = undefined;
    console.log(a.length);  // 1
    
    var a = ["AlphaGL", 18, true];
    a.length = 2;
    console.log(a[2]);      // undefined
    a.length = 0;
    console.log(a[0]);      // undefined
    
    var a = ["AlphaGL", 18, true];
    a.splice(2, 0, "haha"); // 从第2个元素开始,删除0个元素,即添加元素haha
    console.log(a);         // [ 'AlphaGL', 18, 'haha', true ]
    
    a.splice(1, 2);         // 从第1个元素开始,删除2个元素。
    console.log(a);         // [ 'AlphaGL', true ]
    
    a.splice(0, 1, "haha"); // 从第0个元素开始,删除1个元素,并添加haha元素。
    console.log(a);         // [ 'haha', true ]
    

    注:删除数组元素与将数组元素赋值为undefined值类似。使用delete不是修改数组的length属性,也不会移动后继元素位置。其它操作方法基本都会移动数组元素和改变数组length值。也可以直接操作数组的length属性来达到输出元素的目的。push和pop方法提供了类似栈结构的操作。push和shift方法则提供了类似队列结构的操作。splice有替换数组中任意数量的项的作用。

    (4)数组的检测

    var a = ["AlphaGL", 18, true];
    console.log(Array.isArray(a));      // true
    console.log(a instanceof Array);    // true
    

    注:当存在两个以上的全局执行环境时,即存在两个以上不同版本的Array构造函数,instanceof则只能在单一的全局执行环境有效。

    (5)数组的排序

    var a = [1, 11, 57, 7, 23];
    a.sort(function (p1, p2) {  // 使用比较函数来对数组元素进行排序。返回的值小于0,则p1放到p2位置之前;大于0则p1在p2之后;等于0则位置不变。
        return p1 > p2;
    });
    
    console.log(a);     // [ 1, 7, 11, 23, 57 ]
    
    var a = ["AlphaGL", 18, true];
    a.reverse();        // 逆序数组。
    console.log(a);     // [ true, 18, 'AlphaGL' ]
    

    (6)数组的遍历与迭代

    var a = [1, 11, 57, 7, 23];
    var t1 = a.every(function (element, index, array) {
        return element % 2 != 0;
    });
    
    var t2 = a.every(function (element, index, array) {
        return element > 10;
    });
    
    console.log(t1);        // true
    console.log(t2);        // false
    

    注:every方法会对数组中的每一项运行给定函数,如果该函数的每一项都返回true,则结果才为true。

    var a = [1, 11, 57, 7, 23];
    var t1 = a.filter(function (element, index, array) {
        return element % 2 != 0;
    });
    
    var t2 = a.filter(function (element, index, array) {
        return element > 10;
    });
    
    console.log(t1);        // [ 1, 11, 57, 7, 23 ]
    console.log(t2);        // [ 11, 57, 23 ]
    

    注:filter方法会对数组中的每一项运行给定的函数,并返回该函数会返回为true的项组成的新数组。

    var a = [1, 11, 57, 7, 23];
    var t1 = a.forEach(function (element, index, array) {
        array[index] = element + 1;
    });
    console.log(a);         // [ 2, 12, 58, 8, 24 ]
    

    注:forEach方法同样会对数组中每一项运行给定的函数。该方法没有返回值。

    var a = [1, 11, 57, 7, 23];
    var t1 = a.map(function (element, index, array) {
        if(element > 10) {
            return element + 1;
        }
    
        return element - 1;
    });
    
    console.log(t1);        // [ 0, 12, 58, 6, 24 ]
    

    注:map方法会将每次运行给定的函数返回的值,组成一个新的数组。

    var a = [1, 11, 57, 7, 23];
    var t1 = a.some(function (element, index, array) {
        return element > 50;
    });
    
    console.log(t1);        // true
    

    注:map方法同样会对数组中的每一项都运行给定的函数,如果该函数的任一项结果为true,则返回true。

    (7)其它
    当然,数组还有一些其它的用法和函数。这里就不一一介绍了。感兴趣的,可以参考文末列举的参考链接。

    6、函数

    函数,简单描述就是可重复调用多次的功能模块。在JavaScript中,每个函数都是Function类型的实例,因此也一样具有属性和方法。函数名也是对象,可以把函数当作值来使用,这样就提供极大的灵活性。

    6.1 函数的定义

    在JavaScript中,函数的定义有如下几种实现方式:
    (1)function关键字+函数名+参数列表+花括号构成的语句块,例如:

    function foo(p1, p2) {
        return p1 + p2;
    }
    
    console.log(typeof(foo));   // function
    console.log(foo(3, 4));     // 7
    

    (2)使用Function构造函数。一般,不推荐这种使用方法。

    var foo = new Function("p1", "p2", "return p1 + p2");
    
    console.log(foo(3, 4));     // 7
    

    (3)函数表达式

    // 声明了一个匿名函数,并赋值给foo变量。
    var foo = function(p1, p2) {
        return p1 + p2;
    }
    
    console.log(foo(3, 4));     // 7
    
    // 函数表达式也可以包含名称
    var bar = function sum(p) {
        if(p <= 1) {
            return 1;
        }else {
            return p + sum(p - 1);
        }
    }
    
    console.log(bar(5));        // 15
    
    // 声明即调用
    var sum = function(p1, p2) {
        return p1 + p2;
    }(3, 4);
    
    console.log(sum);           // 7
    

    6.2 函数的参数与内部属性

    JavaScript中函数定义并未指定函数参数的类型,调用时也未对实参的值做类型检查,同样也不检查参数个数。

    6.2.1 函数的参数
    function foo(p1, p2, p3) {
        return p2;
    }
    
    console.log(foo(1));                // undefined
    console.log(foo(1, 2));             // 2
    console.log(foo(1, 2, 3));          // 2
    console.log(foo(1, 2, 3, 4));       // 2
    

    当形参与实参的个数不匹配时,少的参数将被置为undefined,多的参数将被丢弃。

    6.2.2 函数的内部属性

    在函数内部,有个特殊的对象arguments。该对象用来保存函数参数,可以像数组样使用数字索引来访问参数,同样它也包含length属性。但它并不是真正的数组。另外,该对象还包含callee属性,该属性指向拥有这个arguments对象的函数。

    function foo(p1, p2, p3) {
        console.log(arguments.length);      // 3
        console.log(arguments[0]);          // 第一个参数,即:1
        console.log(arguments[1]);          // 第二个参数,即:2
        console.log(arguments[2]);          // 第三个参数,即:3
    }
    
    foo(1, 2, 3);
    

    使用arguments和callee:

    function sum(p) {
        if (p <= 1) {
            return 1;
        }else {
            return p + arguments.callee(p -1);
        }
    }
    
    console.log(sum(5));        // 15
    

    6.3 函数的属性

    前面提到过,每个函数都是Function类型的实例,因此也一样具有属性和方法。函数有以下比较常用的属性。

    function foo(p1, p2 , p3) {
        console.log(arguments.length);
        console.log(arguments.callee.length);
    }
    
    console.log(foo.name);          // foo
    console.log(foo.length);        // 3
    foo(1, 2);                      // 2 3
    

    由上可知:
    foo.name:函数的名称。
    foo.length:形参的个数。
    arguments.length:实参的个数。
    arguments.callee.length:形参的个数。

    6.4 闭包

    闭包(closure)是函数型语言的一个重要的特性,许多高级特性都依赖闭包来实现。闭包,是创建在一个函数内部的函数,能够访问函数内部的变量,并保存在内存中,记录上次运行的结果,即保存了创建时的上下文环境信息。因此,可以简单总结为:

    闭包=函数内部创建的函数 + 该函数创建时的上下文环境信息

    例如:

    function counter() {
        var count = 0;
        return function() {
            return count++;
        }
    }
    
    var foo = counter();
    console.log(foo());         // 0
    console.log(foo());         // 1
    console.log(foo());         // 2
    

    闭包的这种机制,就实现面向对象的封装提供了支持。将私有变量封装在内部,提供外包接口函数,来访问该变量。

    构造函数
    函数内部属性
    函数的作用域
    reduce

    7、面向对象

    前面提到过,JavaScript中所有的都是对象。在面向对象编程中,对象是类的实例,而类是具有相同属性和行为的一类对象的抽象和集合。例如:狮子对象是动物这一类型中的一个实例。面向对象编程有三大特性:封装,继承和多态。

    7.1 构造函数

    前面提到过,使用new关键字调用构造函数可以创建一个新对象。

    function Student(name, age) {
        this.name = name;
        this.age = age;
        this.setName = function(n) {
            this.name = n;
        }
    
        this.getName = function() {
            return this.name;
        }
    }
    
    var student = new Student("张三", 18);
    student.setName("李四");
    
    console.log(student.getName());         // 李四
    

    其中,this关键字指向了,当前要创建的对象。

    7.2 原型与继承

    每个对象都有一个私有属性(prototype)原型,该属性指向该对象的原型对象。可以理解为其他编程语言中的,指向基类或者父类的作用。当然,该原型对象同样有一个自己的prototype,层层向上直到该对象的原型为null。null没有原型。JavaScript中几乎所有的对象都是位于原型链顶端的Object的实例,同样可以理解为,都是Object的子类。因此,使用原型对象可以让所有对象实例共享它所包含的属性和方法。

    7.2.1 原型的使用
    function Student(name, age) {
        this.name = name;
        this.age = age;
    
        this.getName = function() {
            return this.name;
        }
    }
    
    var student1 = new Student("张三", 18);
    var student2 = new Student("李四", 18);
    
    console.log(student1.getName == student2.getName);      // false
    

    上面,创建两个不同的对象实例,getName实现了相同的功能,却每个对象中都保留了一份,造成不必要的浪费。这就需要通过原型prototype来解决此问题了。

    function Student(name, age) {
        this.name = name;
        this.age = age;
    
        Student.prototype.getName = function() {
            return this.name;
        }
    }
    
    Student.prototype.country = "china";
    
    var student1 = new Student("张三", 18);
    var student2 = new Student("李四", 18);
    
    console.log(student1.getName == student2.getName);      // true
    console.log(student1.country);                          // china
    console.log(student2.country);                          // china
    
    7.2.2 访问属性规则
    function A() {
    
    }
    
    A.prototype.name = "小明";
    A.prototype.age = 18;
    A.prototype.country = "china";
    
    function B() {
    }
    
    B.prototype = new A();
    B.prototype.name = "小李";
    B.prototype.age = 20;
    
    function C() {
    
    }
    
    C.prototype = new B();
    var c = new C();
    c.name = "小赵";
    c.country = "shanghai";
    
    console.log(c.country);         // shanghai
    console.log(c.age);             // 20
    console.log(c.name);            // 小赵
    

    当访问对象的某个属性时,会根据给定的属性名称来查找。如果,在该对象的实例中找到该属性,则返回属性的值;否则,则继续查找该对象的原型对象,在原型对象中查找该属性,依次层层向上搜索,直到搜索到原型链的末尾。因此,对象的属性会覆盖同名的该对象的原型对象的同名属性。

    7.2.3 isPrototypeOf与instanceof
    function A() {
    }
    
    function B() {
    }
    
    var a1 = new A();
    console.log(a1 instanceof A);                       // true
    console.log(a1 instanceof B);                       // false
    console.log(A.prototype.isPrototypeOf(a1));         // true
    console.log(B.prototype.isPrototypeOf(a1));         // false
    
    A.prototype = {};
    var a2 = new A();
    console.log(a1 instanceof A);                       // false
    console.log(a2 instanceof A);                       // true
    console.log(A.prototype.isPrototypeOf(a1));         // false
    console.log(A.prototype.isPrototypeOf(a2));         // true
    
    B.prototype = new A();
    var a3 = new B();
    console.log(a3 instanceof  A);                      // true
    console.log(a3 instanceof  B);                      // true
    console.log(B.prototype.isPrototypeOf(a3));         // true
    console.log(A.prototype.isPrototypeOf(a3));         // true
    

    通过以上实例可以总结如下:

    • object instanceof constructor
      运算符,用来检测 constructor.prototype是否存在于参数object的原型链。虽然,右操作数是构造函数,但实际上检测了对象的继承关系,而不是检测创建对象的构造函数。

    • prototypeObj.isPrototypeOf(object)
      检查一个对象是否存在于另一个对象的原型链上。可以理解为object对象是否继承自prototypeObj.prototype。

    参考:

    MDN JavaScript教程

  • 相关阅读:
    java 线程的终止与线程中断
    java 线程协作 wait(等待)与 notiy(通知)
    java 线程协作 yield()
    java 线程协作 join()
    python学习 文件操作
    linux 学习 常用命令
    linux 学习 设置固定网Ip
    web 安全
    MySQL数据物理备份之tar打包备份
    MySQL数据物理备份之lvm快照
  • 原文地址:https://www.cnblogs.com/alphagl/p/7966664.html
Copyright © 2011-2022 走看看