美文网首页
RYF javascript笔记1

RYF javascript笔记1

作者: 暗夜的怒吼 | 来源:发表于2016-05-15 21:26 被阅读55次

    标签: 我的笔记

    ---学习资料:http://javascript.ruanyifeng.com/

    1. 导论

    • JavaScript的发明目的,就是作为浏览器的内置脚本语言,为网页开发者提供操控浏览器的能力。

    • 近年来,JavaScript的使用范围,慢慢超越了浏览器,正在向通用的系统语言发展。

    • “任何能够用JavaScript实现的应用,最终都必将用JavaScript实现。”

    2. 基本语法

    2.1 基本句法和变量

    2.1.1 语句

    • JavaScript程序的执行单位为行(line),一般一行就是一个语句。

      var a = 1 + 3 ;

    这是一条语句,其中“1+3”叫做表达式。

    • 语句和表达式的区别在于:前者主要为了进行某种操作,后者则是为了得到返回值

    • 语句以分号结尾表达式不需要分号结尾,否则会变成无任何意义的语句),一个分号就表示一个语句结束。多个语句可以写在一行内。

    • 分号前面可以没有任何内容;,这叫做空语句。

    2.1.2 变量

    • 变量是对“值”的引用,变量有声明和赋值两个步骤。

      var a = 1;

    相当于:

    var a;
    a = 1;
    
    • 只声明变量而不赋值,则该变量的值为undefined。

      var a;
      a //undefined

    • 不声明而直接使用会报错:

      x //ReferenceError: x is not defined

    • JavaScript允许省略var,这样创建的是全局变量,建议总是使用var。

    2.1.3 变量提升

    • JavaScript引擎的工作方式:先解析代码,将所有的变量声明提升(hoisting)到代码的头部,再一行一行地运行。

    比如:

    console.log(a); //undefined
    var a = 1; //var a;被提升
    

    上面代码不会打印出1,但不会报错。因为存在变量提升,所以真正运行的是:

    var a;
    console.log(a);
    a = 1;
    
    • 请注意,变量提升只对var命令声明的变量有效,如果变量不是用var命令声明的就不会发生变量提升。因为JavaScript引擎将其视为对顶层对象的b属性进行赋值。

    比如:

    console.log(b); //ReferenceError: b is not defined
    b = 1; //没有变量声明被提升
    

    2.1.4 标识符

    • 标识符用来识别具体对象。最常见的标识符就是变量名和函数名。
    • JavaScript语言的标识符对大小写敏感。
    • 标识符的命名规则:
    • 第1个字符可以是任意Unicode字母,以及美元符号($)和下划线(_)。
    • 其它字符,还可以用数字。
    • JavaScript的保留字不能用作标识符。
    • 保留字有:var、void、class、if、for、new、null、true、return 等。
    • 还有特别含义的标识符:Infinity、NaN、undefined。

    2.1.5 注释

    • 单行注释,用//起头
    • 多行注释,放在/* 和 */之间

    比如:

    // 单行注释
    /*
     多行
     注释
    */
    

    2.1.6 区块

    • 一组大括号包起来的多条语句,称为“区块”(block)。

    • 与大多数编程语言不一样,JavaScript的区块不构成单独的作用域(scope)。

    比如:

    {
        var a = 1;
    }
    a // 1
    

    类似的C#代码则会报错:

    {
        var a = 1;
    }
    Console.WriteLine(a); //当前上下文中不存在名称“a”
    

    2.1.7 条件语句

    a. if

    if (m === 3) 
      m += 1;
    
    if (m === 0) {
        // ...
    } else if (m === 1) {
       // ...
    } else {
       // ...
    }
    

    else代码块总是跟随离自己最近的那个if语句。

    b. switch

    • 建议case内部必须有break
      因为case内部如果没有break就会接着执行下一个case部分(而且不判断case条件)。

    • switch语句部分和case语句部分,都可以使用表达式。

    • switch后面的表达式与case后面的表示式比较时,采用的是严格相等运算符(===),而不是相等运算符(==)。

    这意味着比较时不会发生类型转换:

    console.log(4=="4");
    console.log(4==="4");
    switch(1 + 3) {
        case "4":
            console.log("string 4");
            break;
        case 2 + 2:
            console.log("number 4");
        case 2 + 3:
            console.log("number 5");
            break;
        default:
            console.log("default");
    }
    

    上面代码会输出:
    true
    false
    number 4 //因为4==="4"是false,而4===4是true
    number 5 //因为case 2 + 2 语句内部没有使用break

    • switch结构不利于代码重用,往往可以用对象形式重写。

    比如:

    var o = {
        banana: function (){ console.log("banana"); },
        apple: function (){ console.log("apple"); },
        default: function (){ console.log("default"); }
    };
    var fruit="apple";
    if (o[fruit]){
        o[fruit]();
    } else {
        o['default']();
    }
    

    输出apple

    2.1.8 循环语句

    a. while循环

    如果条件为真,就不断循环。

    var i = 0;
    while (i<100){
        console.log('i当前为:' + i);
        i++;
    }
    

    b. for循环

    for语句分成三步:

    • 初始化(initialize):确定循环的初始值,只执行一次;

    • 检测(test):检测循环条件,只要为真就进行后续操作;

    • 递增(increment):当循环体执行完毕后,执行递增操作,然后重新检测。

      for (var i=0; i < arr.length; i++) {
      console.log(arr[i]);
      }

    for语句表达式的三个部分可以省略任何一个或全部省略。

    var i=0;
    for (; i < 10; ) {
        console.log(i++);
    }
    

    所有for循环,都可以改写成while循环。

    c. do...while循环

    • do...while循环与while循环类似,唯一的区别就是先运行一次循环体,然后判断循环条件。
    • 另外,while语句后面的分号不能省略:

    至少运行一次,这是这种结构最大的特点。

    do { 
        statement
    } while(expression);
    

    d. break语句和continue语句

    • break语句用于跳出代码块或循环。
    • continue语句用于立即终止本次循环,开始下一次循环。

    e. 标签(label)

    不建议使用。

    2.1.9 结尾的分号

    a. 不使用分号结尾的语句

    }结尾的语句不需要分号结束,如:for、while、if, switch, try、函数
    因此,do...while需要分号结束

    b. 分号的自动添加

    JavaScript引擎在应该写却没写的情况下,会自动添加分号。
    但这难以预测,因此不应省略该有的分号。

    2.2 数据类型

    2.2.1 概述

    六个类别和两个特殊值。

    原始类型:

    数值(number)    //整数和小数
    字符串(string)
    布尔值(boolean)
    

    合成类型:

    对象(object)
    数组(array)
    函数(function)
    

    两个特殊值nullundefined

    2.2.2 typeof运算符

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

    typeof运算符
    instanceof运算符
    Object.prototype.toString方法
    
    1. 原始类型分别返回number、string、boolean
    2. 函数返回function
    3. undefined返回undefined

    利用这一点来检查一个没有声明的变量,而不报错。

    if (typeof v === "undefined"){
        // ...
    }
    
    1. 除此以外,都返回object。

    对象、数组、null

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

    说明数组本质上只是一种特殊的对象。
    null的类型也是object,这是历史原因造成的,它是一个类似于undefined的特殊值。

    区分数组和对象

    console.log(({}) instanceof Array); // false
    console.log([] instanceof Array); // true
    console.log(({}) instanceof Object); // true
    console.log([] instanceof Object); // true
    

    2.2.3 null和undefined

    • 几乎等价。

    如:

    console.log(undefined == null); // true
    console.log(undefined === null); // false
    
    • 大致可以区分:
      null表示"没有对象",即该处不应该有值。
      undefined表示"缺少值",即此处应该有值,但是还未定义。

    • null的特殊之处在于:typeof null 结果为Object

    2.2.4 布尔值

    如果JavaScript预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。

    转换规则是除了下面6个值被转为false,其他值都视为true。

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

    2.2.5 类型转换

    JavaScript是一种动态类型语言,变量是没有类型的,可以随时赋予任意值。但是,数据本身和各种运算是有类型的,因此运算时变量需要转换类型。

    a. 强制转换

    强制转换主要指使用Number、String和Boolean三个构造函数,手动将各种类型的值,转换成数字、字符串或者布尔值。

    Number函数

    1、原始类型:

    • 数值:转换后还是原来的值。
    • 字符串:如果可以被解析为数值,则转换为相应的数值,否则得到NaN。空字符串转为0。
    • 布尔值:true转成1,false转成0。
    • undefined:转成NaN。 null:转成0。

    Number函数和parseInt函数的区别:

    • parseInt逐个解析字符,而Number函数整体转换字符串的类型。
    • Number会忽略八进制的前导0,而parseInt不会。
    • Number函数会自动过滤一个字符串前导和后缀的空格。

    示例:

    parseInt('011') // 9
    parseInt('42 cats') // 42
    parseInt('0xcafebabe') // 3405691582
    
    Number('011') // 11
    Number('42 cats') // NaN
    Number('0xcafebabe') // 3405691582
    

    2、对象

    • 1.先调用对象自身的valueOf方法,如果该方法返回原始类型的值(数值、字符串和布尔值),则直接对该值使用Number方法,不再进行后续步骤。
    • 2.如果valueOf方法返回复合类型的值,再调用对象自身的toString方法,如果toString方法返回原始类型的值,则对该值使用Number方法,不再进行后续步骤。
    • 3.如果toString方法返回的是复合类型的值,则报错。
    String函数

    1、原始类型:

    • 数值:转为相应的字符串。
    • 字符串:转换后还是原来的值。
    • 布尔值:true转为“true”,false转为“false”。
    • undefined:转为“undefined”。 null:转为“null”。

    2、对象:

    • 先调用toString方法,如果toString方法返回的是原始类型的值,则对该值使用String方法,不再进行以下步骤。
    • 如果toString方法返回的是复合类型的值,再调用valueOf方法,如果valueOf方法返回的是原始类型的值,则对该值使用String方法,不再进行以下步骤。
    • 如果valueOf方法返回的是复合类型的值,则报错。

    String方法的这种过程正好与Number方法相反。

    Boolean函数

    1、原始类型:

    以下六个值的转化结果为false,其他的值全部为true。

    • undefined
    • null
    • -0
    • +0
    • NaN
    • ''(空字符串)

    2、对象:

    所有对象的布尔值都是true,甚至连false对应的布尔对象也是true。
    空对象{}和空数组[]也会被转成true。

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

    b. 自动转换

    有时JavaScript会自动转换数据类型,比如:
    不同类型的数据进行互相运算;
    对非布尔值类型的数据求布尔值,等。

    1、自动转换为布尔值

    除了以下6个值,其他都是自动转为true:

    • undefined
    • null
    • -0
    • +0
    • NaN
    • ''(空字符串)

    :

    if (!undefined && !null && !0 && !NaN && !''){
        console.log('true');
    }
    // true
    

    2、自动转换为字符串
    字符串的自动转换,主要发生在加法运算时。

    '5' + 1 // '51'
    '5' + true // "5true"
    '5' + {} // "5[object Object]"
    '5' + [] // "5"
    '5' + function (){} // "5function (){}"
    '5' + undefined // "5undefined"
    '5' + null // "5null"
    

    3、自动转换为数值

    当JavaScript遇到预期为数值的地方,就会将参数值自动转换为数值,转换规则与“强制转换为数值”相同。

    除了加法运算符有可能把运算子转为字符串,其他运算符都会把两侧的运算子自动转成数值。

    '5' - '2' // 3
    '5' * '2' // 10
    true - 1  // 0
    false - 1 // -1
    '1' - 1   // 0
    '5' * []    // 0
    false / '5' // 0
    'abc' - 1   // NaN
    

    JavaScript的两个一元算术运算符也会把运算子自动转为数值。

    +'abc' // NaN
    -'abc' // NaN
    +true // 1
    -false // 0
    

    c. 加法运算符的类型转化

    加法运算符(+)需要特别讨论,因为它可以完成两种运算(加法和字符连接),所以不仅涉及到数据类型的转换,还涉及到确定运算类型。

    2、三种情况

    • 运算子之中有字符串

    则另一个被转为字符串,然后执行字符串连接运算。

    • 运算子都为数值或布尔值,则执行加法运算

    布尔值转为数值(true为1,false为0)。

    true + 5 // 6 
    true + true // 2
    
    • 运算子之中存在对象

    太复杂,略。

    2.3 运算符

    2.3.1 算数运算符

    • 加法运算符(Addition):x + y
      加法运算符(+)需要注意的地方是,它除了用于数值的相加,还能用于字符串的连接。

    • 减法运算符(Subtraction): x - y

    • 乘法运算符(Multiplication): x * y

    • 除法运算符(Division):x / y

    • 余数运算符(Remainder):x % y

    运算结果的正负号由第一个运算子的正负号决定(有负数的话建议先使用绝对值)

    12 % 5 // 2
    -1 % 2 // -1
    1 % -2 // 1
    
    // 判断偶数
    function isOdd(n) {
        return Math.abs(n % 2) === 1;
    }
    
    • 自增运算符(Increment):++x 或者 x++

    • 自减运算符(Decrement):--x 或者 x--

    • 求负运算符(Negate):-x

    • 数值运算符(Convert to number): +x

    作用是将任何值转为数值(与Number函数作用相同)。

    +true // 1
    +[] // 0
    +{} // NaN
    

    2.3.2 赋值运算符

    =、 += 、 -= 、 *= 、 /= 、 %= 、 >>= 、 <<= 、 >>>= 、 &= 、 |= 、 ^=

    2.3.2 比较运算符

    == 相等
    === 严格相等
    != 不相等
    !== 严格不相等
    < 小于
    <= 小于或等于

    大于
    = 大于或等于

    相等运算符(==)比较两个“值”是否相等,严格相等运算符(===)比较它们是否为“同一个值”。
    两者的一个重要区别是,如果两个值不是同一类型,严格相等运算符(===)直接返回false,而相等运算符(==)会将它们转化成同一个类型,再用严格相等运算符进行比较。

    我们先看严格相等运算符。

    严格相等运算符

    1、不同类型的值

    如果两个值的类型不同,直接返回false。

    2、同一类的原始类型值

    同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回true,值不同就返回false。

    1 === 0x1 // true
    
    • NaN与任何值都不相等(包括自身)。
    • 正0等于负0。

    3、同一类的复合类型值
    复合类型(对象、数组、函数)的数据比较时,是比较它们是否指向同一个对象(内存地址)。

    ({}) === {} // false
    [] === [] // false
    (function (){}) === function (){} // false
    
    var v1 = {};
    var v2 = v1;
    v1 === v2 // true
    

    4、undefined和null

    undefined 和 null 与自身严格相等。

    undefined === undefined // true
    null === null // true
    

    由于变量声明后默认值是undefined,因此两个只声明未赋值的变量是相等的。

    var v1;
    var v2;
    v1 === v2 // true
    

    相等运算符

    相等运算符在比较相同类型的数据时,与严格相等运算符完全一样。

    在比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较。类型转换规则如下:

    1、原始类型的值

    原始类型的数据会转换成数值类型再进行比较。

    1 == true // true
    "true" == true // false
    '' == 0 // true
    '1' == true  // true
    
    // 注意:字符串转为数字时,省略前置和后置的空格
    '\n  123  \t' == 123 // true
    '  123  ' == 123 // true
    ' 123' == 123 // false
    

    2、对象与原始类型值比较

    对象(这里指广义的对象,包括数值和函数)与原始类型的值比较时,对象转化成原始类型的值,再进行比较。

    [1] == 1 // true
    [1] == "1" // true
    [1] == true // true
    

    3、undefined和null

    undefined和null与其他类型的值比较时,结果都为false,它们互相比较时结果为true。

    false == null // false
    0 == null // false
    
    undefined == null // true
    

    4、相等运算符的缺点
    相等运算符隐藏的类型转换,会带来一些违反直觉的结果。

    '' == '0'           // false
    0 == ''             // true
    0 == '0'            // true
    
    false == 'false'    // false
    false == '0'        // true
    
    false == undefined  // false
    false == null       // false
    null == undefined   // true
    
    ' \t\r\n ' == 0     // true
    

    上面这些表达式都很容易出错,因此不要使用相等运算符(==),最好只使用严格相等运算符(===)。

    布尔运算符

    布尔运算符用于将表达式转为布尔值。

    1、取反运算符(!)

    对于非布尔值的数据,取反运算符会自动将其转为布尔值。规则是,以下六个值取反后为true,其他值取反后都为false。

    undefined
    null
    false
    0(包括+0和-0)
    NaN
    空字符串("")
    

    两次取反就是将一个值转为布尔值的简便写法。

    !!x
    // 等同于
    Boolean(x)
    

    2、且运算符(&&)
    “短路”机制

    3、或运算符(||)

    4、三元条件运算符( ? :)

    "t" ? true : false // true
    

    5、位运算符

    5.1 简介
    或运算(or)|
    与运算(or)&
    否运算(not)~
    异或运算(xor)ˆ
    左移运算(left shift)<<
    右移运算(right shift)>>
    带符号位的右移运算(zero filled right shift)>>>

    略。

    其他运算符

    1、圆括号运算符() 表示求值:

    有2种用法:
    把表达式放在圆括号之中,作用是求值;
    跟在函数的后面,作用是调用函数。

    (2) // 2
        
    function f(){return 1;}
    (f) // function f(){return 1;}
    f() // 1
    
    // 语句没有返回值所以不能放在括号里
    (var a =1) // SyntaxError: Unexpected token var
    

    2、void运算符 作用是执行一个表达式,然后返回undefined。

    void (0) // undefined
    

    这个运算符主要是用在超级链接中插入代码,目的是返回undefined可以防止网页跳转。

    javascript:void window.open("http://example.com/")
    

    比如:

    <a href="#" onclick="f();return false;">文字</a>
    

    void运算符可以取代上面两种写法。

    <a href="javascript:void(0)" onclick="f();">文字</a>
    

    3、逗号运算符

    逗号运算符用于对两个表达式求值,并返回后一个表达式的值。

    "a", "b" // "b"
    
    var x = 0;
    var y = (x++, 10);
    x // 1
    y // 10
    

    运算顺序

    运算符是有优先级的
    圆括号的优先级最高(建议总是使用圆括号)

    左结合与右结合
    大部分运算符是左结合,
    少数是右结合,最主要的是:=和三元运算符(?:)。

    w = x = y = z;
    q = a?b:c?d:e?f:g;
    

    相当于:

    w = (x = (y = z)); 
    q = a?b:(c?d:(e?f:g));
    

    2.4 数值

    数字都以64位浮点数形式储存。

    2.4.1 精度和取值范围

    整数在绝对值<=253(Math.pow(2, 53))内的都可以精确表示。
    浮点数取值范围在21024到2-1024(开区间)之间。

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

    0.1 + 0.2 === 0.3
    // false
    
    0.3 / 0.1
    // 2.9999999999999996
    

    2.4.2 数值的表示法

    可以用字面形式直接表示,也可以采用科学计数法表示。

    123e3 // 123000
    123e-3 // 0.123
    

    正常情况下,所有数值都为十进制。
    十六进制必须以0x或0X开头。
    八进制数必须以0开头(八进制很容易造成混乱)。

    2.4.3 特殊数值

    1、零
    JavaScript提供零的3种写法:0、+0、-0。它们是等价的。
    但是,如果正零和负零分别当作分母,它们返回的值是不相等的。

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

    因为除以正零得到+Infinity,除以负零得到-Infinity,这两者是不相等的(关于Infinity详见后文)。

    2、NaN
    NaN表示“非数字”(Not a Number)。
    NaN不等于任何值,包括它本身。
    NaN与任何数(包括它自己)的运算,得到的都是NaN。

    isNaN函数可以用来判断一个值是否为NaN。
    但是,isNaN只对数值有效,如果传入其他值,会被先转成数值。也就是说,isNaN为true的值,有可能不是NaN,而是一个字符串。

    isNaN("Hello") // true
    

    因此,使用isNaN之前,最好判断一下数据类型。

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

    或者利用NaN是唯一不等于自身的值这个特点,进行判断。

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

    3、Infinity
    Infinity表示“无穷”。除了0除以0得到NaN,其他任意数除以0,得到Infinity。

    1 / -0 // -Infinity
    1 / +0 // Infinity
    

    运算结果超出JavaScript可接受范围,也会返回无穷。

    JavaScript的数学运算几乎没有可能抛出错误。

    isFinite函数返回一个布尔值,检查某个值是否为正常值,而不是Infinity。

    isFinite(Infinity) // false
    isFinite(NaN) // false
    

    2.4.4 与数值相关的全局方法

    1、parseInt
    将字符串或小数转化为整数。字符串头部空格会被自动去除。一旦遇到不能转化的字符就停止继续转化。
    如果字符串的第一个字符不能转化为数字(正负号除外),返回NaN。

    parseInt('   1a23') // 1
    
    parseInt("abc") // NaN
    parseInt(".3") // NaN
    

    parseInt还可以接受第二个参数(2到36之间),表示被解析的值的进制。这意味着,可以用parseInt方法进行进制的转换。

    parseInt(1000, 2) // 8
    

    2、parseFloat
    将字符串转为浮点数。同样,字符串头部空格会被自动去除。一旦遇到不能转化的字符就停止继续转化。
    如果字符串的第一个字符不能转化为浮点数,返回NaN。

    parseFloat("   3.14"); // 3.14
    
    parseFloat("a2") // NaN
    parseFloat("") // NaN
    

    parseFloat将空字符串转为NaN。

    parseFloat的转换结果不同于Number函数。

    parseFloat(true)  // NaN
    Number(true) // 1
    parseFloat(null) // NaN
    Number(null) // 0
    parseFloat('') // NaN
    Number('') // 0
    parseFloat('123.45#') // 123.45
    Number('123.45#') // NaN
    

    2.5 字符串

    2.5.1 概述

    字符串默认只能写在一行内,如果必须分成多行,可以在每一行的尾部使用反斜杠(ECMAScript 5新添加),而且反斜杠的后面必须是换行符,不能有其他字符(比如空格)。

    var longString = "Long \
    string";
    

    连接运算符(+)可以连接多个单行字符串。

    var longString = "Long " + "string";
    

    转义

    \n 换行符
    \r 回车键
    \t 制表符
    \' 单引号
    \" 双引号
    \\ 反斜杠
    

    字符串与数组

    字符串可以被视为字符数组,因此可以使用数组的方括号运算符。

    'hello'[0] // "h"
    'hello'[5] // undefined

    但是,实际上,字符串是类似数组的对象,且无法改变字符串之中的单个字符。length属性也是无法改变的。

    var s = 'hello';
    
    delete s[0];
    s[1] = 'a';
    s.length = 3;
    s // "hello"
    

    字符串内部的单个字符无法改变和增删,这些操作会默默地失败。字符串的length也无法改变,但不会报错。

    字符串也无法添加新属性:

    var s = "Hello World";
    s.x = 123;
    s.x // undefined
    

    2.5.2 字符集

    JavaScript内部所有字符都用Unicode表示。所有字符都可以写成"\uxxxx"的形式。

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

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

    var s = "\uD834\uDF06"
    
    s // "𝌆"
    s.length // 2
    s.charAt(0) // ""
    s.charCodeAt(0) // 55348
    

    由于JavaScript引擎(ES5)不能自动识别编号大于0xFFFF的Unicode字符,导致所有字符串处理函数遇到这类字符,都会产生错误的结果,所以必须判断字符是否落在0xD800到0xDFFF这个区间。

    下面是能够正确处理字符串遍历的函数:

    function getSymbols(string) {
      var length = string.length;
      var index = -1;
      var output = [];
      var character;
      var charCode;
      while (++index < length) {
        character = string.charAt(index);
        charCode = character.charCodeAt(0);
        if (charCode >= 0xD800 && charCode <= 0xDBFF) {
          output.push(character + string.charAt(++index));
        } else {
          output.push(character);
        }
      }
      return output;
    }
    

    替换(String.prototype.replace)、截取子字符串(String.prototype.substring, String.prototype.slice)等其他字符串操作,都必须做类似的处理。

    2.5.3 Base64转码

    window.btoa("Hello World")
    // "SGVsbG8gV29ybGQ="
    window.atob("SGVsbG8gV29ybGQ=")
    // "Hello World"
    

    这两个方法不适合非ASCII码的字符,浏览器会报错。

    window.btoa('你好')
    // InvalidCharacterError: An invalid or illegal character was specified, such as in an XML name.
    

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

    function b64Encode( str ) {
        return window.btoa(unescape(encodeURIComponent( str )));
    }
     
    function b64Decode( str ) {
        return decodeURIComponent(escape(window.atob( str )));
    }
    
    // 使用方法
    b64Encode('你好') // "5L2g5aW9"
    b64Decode('5L2g5aW9') // "你好"
    

    2.6 对象

    2.6.1 概述

    所谓对象,就是一种无序的数据集合,由若干个“键值对”(key-value)构成。
    JavaScript的所有数据都可以被视为对象。

    var o = {
        id: 123,
        "na me": "Hello"
    };
    

    键名加不加引号都可以。但是,如果键名不符合标识名的条件(比如包含数字、字母、下划线以外的字符,或者第一个字符为数字),也不是正整数,则必须加上引号。

    如果一个属性的值为函数,通常把这个属性称为“方法”。

    2.6.1.1 生成方法

    var o1 = {};
    var o2 = new Object();
    var o3 = Object.create(null);
    

    2.6.1.2 读取属性

    有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。

    var o = {
        id: "Hello",
        0.7: "World"
    };
    
    o.id // "Hello"
    o["id"] // "Hello"
    o["i" + "d"] // "Hello"
    o["0.7"] // "World"
    o[0.7] // "World"
    

    注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。但是,数字键可以不加引号,因为会被当作字符串处理。

    2.6.1.3 检查变量是否声明

    读取一个不存在的键,会返回undefined,而不是报错。
    可以使用in运算符判断属性是否存在(也可以判断变量是否声明)。

    "id" in o // true
    0.8 in o // false
    o in window // true
    

    在浏览器环境,所有全局变量都是window对象的属性。

    2.6.1.4 写入属性

    同样使用:点运算符和方括号运算符。

    o.p = "abc";
    o["p"] = "abc";
    

    JavaScript允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。

    var o = { p:1 };
    
    // 等价于
    var o = {};
    o.p = 1;
    

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

    Object.keys(o);
    // ["id", "0.8"]
    

    2.6.1.6 属性的删除(delete)

    delete o.p // true
    o.p // undefined
    

    一旦使用delete命令删除某个属性,再读取该属性就会返回undefined,而且Object.keys方法返回的该对象的所有属性中,也将不再包括该属性。

    注意:如果删除一个不存在的属性,delete不报错,而且返回true。

    只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。

    var o = Object.defineProperty({}, "p", {
            value: 123,
            configurable: false
    });
    
    o.p // 123
    delete o.p // false
    

    另外,需要注意的是,delete命令只能删除对象本身的属性,不能删除继承的属性。delete命令也不能删除var命令声明的变量,只能用来删除对象的属性和方法。

    2.6.1.7 对象的引用

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

    var o1 = {};
    var o2 = o1;
    
    o1.a = 1;
    o2.a // 1
    

    这种引用只局限于对象,对于原始类型的数据则是值的拷贝。

    2.6.1.8 in运算符

    in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值)。

    var o = { p: 1 };
    'p' in o // true
    

    该运算符对数组也适用。

    var a = ["hello", "world"];
    0 in a // true
    '0' in a // true
    

    在JavaScript语言中,所有全局变量都是顶层对象(浏览器环境的顶层对象为window)的属性,因此可以用in运算符判断一个全局变量是否存在。

    if ('x' in window) { return 1; }
    

    in运算符对继承的属性也有效。

    var o = new Object();
    o.hasOwnProperty('toString')  // false
    'toString' in o // true
    

    in运算符只能用来检验可枚举(enumerable)的属性。

    2.6.1.9 for...in循环

    var o = {a:1, b:2, c:3};
    for (i in o){
        console.log(o[i]);
    }
    

    for...in循环遍历的是对象所有可enumberable的属性,其中不仅包括定义在对象本身的属性,还包括对象继承的属性。

    如果只想遍历对象本身的属性,可以使用hasOwnProperty方法做一个判断。

    for (var key in person) {
        if (person.hasOwnProperty(key)) {
            console.log(key);
        }
    }
    

    2.6.2 类似数组的对象

    在js中,有些对象被称为“类似数组的对象”,它们可以使用length属性,但是它们并不是数组,无法使用一些数组的方法。

    var a = {
        0:'a',
        1:'b',
        length:2
    };
    a[0] // 'a'
    a.length // 3
    

    变量a就是一个类似数组的对象,但它无法使用数组特有的一些方法,比如pop和push方法。而且,length属性不是动态值。

    典型的类似数组的对象是:函数的arguments对象,以及大多数DOM元素集,还有字符串

    // arguments对象
    function args() { for(var i=0;i<arguments.length;i++) alert(arguments[i]); }
    args('a', 'b');
    
    // DOM元素集
    var elts = document.getElementsByTagName('h3');
    
    // 字符串
    'abc'[1] // 'b'
    'abc' instanceof Array // false
    

    通过函数的call方法,可以用slice方法将类似数组的对象,变成真正的数组

    var arr = Array.prototype.slice.call("abc");
    

    遍历类似数组的对象,可以采用for循环,也可以采用数组的forEach方法。

    // for循环
    for(var i=0;i<"abc".length;i++){
        console.log(i+'. '+"abc"[i]);
    };
    
    // forEach方法
    Array.prototype.forEach.call("abc", function (elem, i) {
        console.log(i+'. '+elem);
    });
    

    2.6.3 with语句(不建议使用)

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

    o.p1 = 1;
    o.p2 = 2;
    // 等同于
    with (o){
        p1 = 1;
        p2 = 2;
    }
    

    注意:在with区块内部依然是全局作用域。这意味着,如果你要在with语句内部,赋值对象某个属性,这个属性必须已经存在,否则你就是声明了一个全局变量(绑定对象不明确)。

    2.7 数组

    2.7.1 数组的定义

    数组(array)是按次序排列的一组值,单个值称为元素。

    1. 编号从0开始。
    2. 可变长。可先定义再赋值。
    3. 元素可以是任何类型。

    例如:

    var arr = [];
    arr[0] = {a:1}; // 对象
    arr[1] = [1,2,3]; // 数组
    arr[2] = function (){return true;}; // 函数
    

    如果数组的元素还是数组,就形成了多维数组。

    var a = [[1,2],[3,4],"x"];
    console.log(a[0][0]);
    console.log(a[2]);
    

    2.7.2 数组与对象的关系

    本质上,数组也属于对象。

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

    上面代码表明,数组只是一种特殊的对象,所以typeof运算符返回数组的类型是object。

    数组是字典结构的一个变种,它的键是0开始的整数。

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

    上面代码分别用数值和字符串作为键名,结果都能读取数组。

    需要注意的是,这一条在赋值时也成立,即如果一个值可以被转换为整数,则以该值为键名,等于以对应的整数为键名。

    var a = [];
    a[0] = 0;
    a['1000'] = 'abc';
    console.log(a.length); // 1001
    

    对于数字的键名,不能使用点结构,所以arr.0的写法不合法。因此数组只能用方括号arr[0]表示。

    2.7.3 length属性

    • 数组的length是一个动态的值。它是最大键值+1.
    • length属性是可写的。设置length可以改变数组的大小,空位都填充为undefined。
      如果设置length为不合法的值,JavaScript会报错。
    • 可以为数组添加属性(因为数组本质上是对象),但这不影响length的值

    示例:

    var a = [];
    a[1] = 'abc';
    a['10'] = 'abc'; //a.length是10
    a.length=3; //a.length是3
    console.log(a); // [undefined, "abc", undefined]
    a.id="ddd"; //a.length是3
        
    [].length = -1 // RangeError: Invalid array length
    [].length = 'abc' // RangeError: Invalid array length
    

    2.7.4 数组的空位

    • delete命令可以删除值,形成空位。
      注意:使用delete命令删除值,不影响length属性。

    • 空位通过空值生成,还是通过显式设为undefined生成,有细微的差别。

    如果通过空值生成,使用数组的forEach方法或者for...in结构进行遍历,空位就会被跳过。

    var a = [0,1,,3,undefined];
    delete a[1];
    console.log(a.length); // 5
    a.forEach(function (x, i) { console.log(x) }) // 0,3,undefined 
    for (var i in a){console.log(a[i])} // 0,3,undefined 
    

    2.7.5 in运算符,for...in循环

    • in可以坚持是否存在某个键
      检查某个键是否存在的运算符in,适用于对象,也适用于数组。
    • for-in循环,可以遍历数组的所有元素
      注意:for-in会遍历数组所有的键,即使是非数字键。
    • 还可以用for或while遍历数组

    示例:

    var a = [0,,undefined];
    console.log(0 in a); // true
    console.log(1 in a); // false
    console.log(2 in a); // true
    a.id = function (){};
    for (var i in a){console.log(a[i])} // 0,undefined,function (){} 
    for(var i = 0; i < a.length; i++){console.log(a[i]);} // 0,undefined,undefined 
    

    2.7.6 Array构造函数

    数组还可以使用JavaScript内置的Array构造函数创建。

    var a = new Array(2);
    console.log(a); // [undefined, undefined]
    var a = new Array(1,2);
    console.log(a); // [1, 2]
    

    2.8 函数

    2.8.1 概述

    2.8.1.1 函数的声明

    1、function命令声明函数

    function print(){
        // ...
    }
    

    2、函数表达式

    var print = function (){
        // ...
    };
    

    采用函数表达式声明函数时,function命令后面不带有函数名。
    如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。

    3、Function构造函数

    var add = new Function("x","y","return (x+y)");
    

    最后一个参数是add函数的“函数体”,其他参数都是add函数的参数。

    4、函数的重复声明

    重复声明同一个函数,则后面的声明会覆盖前面的声明。
    由于存在函数名的提升,所以前面的函数声明完全是无效的。

    2.8.1.2 圆括号运算符和return语句

    调用函数时,要使用圆括号运算符。圆括号之中,可以加入函数的参数。
    参数数量可以不匹配!

    function add(x,y) {
        return x+y;
    }
    
    console.log(add(1,2,3)) // 3
    console.log(add(1)) // NaN
    

    return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined。

    函数调用自身,这就是递归。

    2.8.1.3 第一等公民

    JavaScript的函数与其他数据类型处于同等地位,可以使用其他数据类型的地方就能使用函数。
    比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。
    这表示函数与其他数据类型的地方是平等,所以又称函数为第一等公民。

    2.8.1.4 函数名的提升

    JavaScript引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会被提升到代码头部。

    f();
    var f = function (){ alert("aa";) };
    

    但是,如果采用赋值语句定义函数就会报错,因为提升的只是赋值语句左边的变量,函数并未被提升。

    f();
    var f = function (){}; // TypeError: undefined is not a function
    

    2.8.1.5 不能在条件语句中声明函数

    不得在非函数的代码块中声明函数,最常见的情况就是if和try语句。
    按照语言规范,这是不合法的。但是,实际情况是各家浏览器往往并不报错,能够运行。

    2.8.1.6 name属性

    大多数JavaScript引擎支持非标准的name属性。该属性返回紧跟在function关键字之后的那个函数名。

    function f1() {}
    f1.name // 'f1'
    
    var f2 = function () {};
    f2.name // ''
    

    2.8.2 函数作用域

    2.8.2.1 定义

    Javascript只有两种作用域:一种是全局作用域,变量在整个程序中一直存在;另一种是函数作用域,变量只在函数内部存在。

    函数内部定义的变量,会在该作用域内覆盖同名全局变量。

    var w = 1; 
    var w2 = 1;
    (function f(){
       w=2;
       var w2 = 2;
       var n = 1;
    })();
    console.log(w); // 2
    console.log(w2); // 1
    console.log(n); // ReferenceError: n is not defined
    

    2.8.2.2 函数内部的变量提升

    与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。

    2.8.3 参数

    2.8.3.1 概述

    函数可以定义参数。
    函数对象的length属性,返回函数定义中参数的个数。

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

    2.8.3.2 参数的省略

    函数调用时,提供多少参数都不会报错。被省略的参数的值就变为undefined。多的废弃。
    没有办法只省略靠前的参数,想那样只有显式传入undefined。

    f(undefined,1) 
    

    2.8.3.3 默认值

    如果要为参数设置默认值,可以使用方法:

    function f(a){
        (a !== undefined && a != null)?(a = a):(a = 1);
        return a;
    }
    console.log(f('')); // ""
    console.log(f(0)); // 0
    console.log(f()); // 1
    console.log(f(null)); // 1
    

    因为undefined、0、空字符、null等的布尔值都是false。

    2.8.3.4 传值方式

    js的函数参数传递方式是值传递,这意味着,在函数体内修改参数值,不会影响到函数外部。
    但是:对于复合类型的变量来说,属性值是通过地址读取的。所以在函数体内修改复合类型变量的属性值,会影响到函数外部。

    var o = { p:1 };
    function f(obj){
        obj.p = 2;
    }
    f(o);
    o.p // 2
    
    var a = [1,2,3];
    function f(a){
        a[0]=4;
    }
    f(a);
    a // [4,2,3]
    

    如果需要对某个变量达到按址传递的效果,可以将它写成全局对象的属性。

    var a = 1;
    var b = 2;
    function f(x,y){
        x *= 10;
        window[y] *= 10;
    }
    f(a,'b'); // 注意:第二个参数不能是b,这样的话函数f内会去找window["2"]这个属性。
    console.log(a); // 1
    console.log(b); // 20
    

    2.8.3.5 同名参数

    如果有同名的参数,则取最后出现的那个值。
    但是用arguments可以取到正确的参数。

    f(1,2);
    function f(a, a){
        console.log(a+a); //4
        console.log(arguments[0] + arguments[1]); //3
    }
    

    2.8.3.6 arguments对象

    • arguments可以在函数体内部读取所有参数
      也可设置,严格模式不允许这种用法。
    • arguments对象的length属性,表示函数调用时到底带几个参数
    • arguments对象的callee属性,返回它所对应的原函数
      (注意:不是那个指向函数的变量)。

    示例:

    var f = function f2(a) {
       console.log(arguments[0]); // a
       console.log(arguments[1]); // b
       console.log(arguments.length); // 3
       console.log(arguments.callee === f); // true
       console.log(arguments.callee === f2); // true
    };
    f("a","b","c");
    

    2.8.4 函数的其他知识点

    2.8.4.1 闭包

    闭包是函数与其生成时所在的作用域对象的一种结合。

    闭包可以读取函数内部变量,还可以使得内部变量记住上一次调用时的运算结果。

    function f(start) {
        return function () { 
            return start++;
        }
    }
    var inc = f(5);
    console.log(inc());  // 5
    console.log(inc());  // 6
    console.log(inc());  // 7
    

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

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

    这两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义。

    2.8.5 eval命令

    eval命令的作用是,将字符串当作语句执行。
    放在eval中的字符串,应该有独自存在的意义,不能用来与eval以外的命令配合使用。

    eval('var a = 1;');
    eval('return;'); //报错
    

    2.9 错误处理机制

    2.9.1 Error对象js

    一旦发生错误,js引擎会抛出一个Error对象的实例,然后程序就此中断。

    Error对象有3个基本的属性:name、message、stack。

    (function catchit() {
       try {
          throw new Error('出错了.'); // Error
       } catch(e) {
          console.log(e.name); // Error
          console.log(e.message); //出错了
          console.log(e.stack); // print stack trace
       }
    })();
    

    2.9.2 JavaScript的原生错误类型

    Error对象是最一般的错误类型,还存在Error的6个派生对象。

    • SyntaxError是解析代码时发生的语法错误。
    • ReferenceError是引用一个不存在的变量时发生的错误。
    • TypeError是变量或参数不是预期类型时发生的错误。
      比如,对字符串、布尔值、数值等原始类型的值使用new命令。因为new命令的参数应该是一个构造函数。
    • RangeError是当一个值超出有效范围时发生的错误。
      主要有:一是数组长度为负数,二是Number对象的方法参数超出范围
    • URIError是URI相关函数的参数不正确时抛出的错误,主要涉及encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和unescape()这六个函数。
    • EvalError是eval函数没有被正确执行时,会抛出EvalError错误。

    示例:

    var 1a; // SyntaxError 变量名错误
    new Array(-1) // RangeError  数组长度为负
    new 123 // TypeError 123不是函数
    decodeURI('%2') // URIError
    

    2.9.3 自定义错误

    function UserError(message) {
       this.message = message || "默认信息";
       this.name = "UserError";
    }
    UserError.prototype = new Error();
    UserError.prototype.constructor = UserError;
    

    自定义一个错误对象UserError,让它继承Error对象。然:

    new UserError("这是自定义的错误!");
    

    2.9.4 throw语句

    throw语句的作用是中断程序执行,抛出一个错误。它接受一个表达式作为参数。

    throw "Error!"; 
    

    2.9.5 try...catch结构和finally代码块

    try {
        throw new Error('出错了!');
    } catch (e) {
        console.log(e.name + ": " + e.message); 
        console.log(e.stack);  
    }finally {
        console.log('无论是否出错都执行');
    }
    

    甚至可以省略catch代码块,只使用finally代码块。

    try {
       ....
    } finally {
       console.log('无论是否出错都执行');
    }
    

    相关文章

      网友评论

          本文标题:RYF javascript笔记1

          本文链接:https://www.haomeiwen.com/subject/abioxttx.html