A22-JS里的对象

  • 全局对象 window

    ECMAScript 规定全局对象叫做 global,但是浏览器把 window 作为全局对象(浏览器先存在的)
    window 就是一个哈希表,有很多属性。
    window 的属性就是全局变量。
    使用window的属性可以不加window.,
    比如window.alert('警告')可以写成alert('警告')
    这些全局变量分为两种:

    一种是 ECMAScript 规定的

    • global.parseInt
    • global.parseFloat
    • global.Number
    • global.String
    • global.Boolean
    • global.Object

    一种是浏览器自己加的属性

    • window.alert
    • window.prompt
    • window.comfirm
    • window.console.log
    • window.console.dir
    • window.document
      // document由浏览器实现,但是也有一个自己的标准,那就是 DOM,它的标准由 W3C 制定
    • window.document.createElement
    • window.document.getElementById
      所有 API 都可以在 MDN 里找到详细的资料。
  • 全局函数&简单类型与对象的区别

    1. Number()

    • MDN
    • 阮一峰
    • Number('1') // 1 强制转换
    • var n = new Number(1) // 返回一个值为1的对象
      在这里,Number()对象作为构造函数使用,用来生成值为数值的对象。
    • 和直接赋值数字也就是简单类型的区别:

      • 一个是简单类型,一个是复杂类型
      • 两者对内存的使用不同,假设有
        var n1 = 1
        var n2 = new Number(1)
        内存图:
      • 包装对象有许多便捷的操作
      • 你可以通过valueOf()来获得n2的原始值1
      • 其它还有

        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
        toString() // 获得字符串形式
        toFixed() // 转为指定位数的小数,返回这个小数对应的字符串
        toExponential() // 将一个数转为科学计数法形式
        toPrecision() // 将一个数转为指定位数的有效数字

        (10).toString() // "10"
        // toString方法可以接受一个参数,表示输出的进制。如果省略这个参数,默认将数值先转为十进制,再输出字符串;否则,就根据参数指定的进制,将一个数字转化成某个进制的字符串。
        (10).toString(2) // "1010"
        (10).toString(8) // "12"
        (10).toString(16) // "a"

        // 上面代码中,之所以要把10放在括号里,是为了表明10是一个单独的数值,后面的点表示调用对象属性。如果不加括号,这个点会被JavaScript引擎解释成小数点,从而报错。

        // 只要能够让JavaScript引擎不混淆小数点和对象的点运算符,各种写法都能用。除了为10加上括号,还可以在10后面加两个点,JavaScript会把第一个点理解成小数点(即10.0),把第二个点理解成调用对象属性,从而得到正确结果。
        10..toString(2) // "1010"

        // 其他方法还包括
        10 .toString(2) // "1010"
        10.0.toString(2) // "1010"

        // toFixed方法用于将一个数转为指定位数的小数,返回这个小数对应的字符串。
        (10).toFixed(2) // "10.00"
        10.005.toFixed(2) // "10.01"

        // toExponential方法的参数表示小数点后有效数字的位数,范围为0到20,超出这个范围,会抛出一个RangeError。
        (10).toExponential() // "1e+1"
        (10).toExponential(1) // "1.0e+1"
        (10).toExponential(2) // "1.00e+1"

        (1234).toExponential() // "1.234e+3"
        (1234).toExponential(1) // "1.2e+3"
        (1234).toExponential(2) // "1.23e+3"

        // toPrecision方法的参数为有效数字的位数,范围是1到21,超出这个范围会抛出RangeError错误。
        (12.34).toPrecision(1) // "1e+1"
        (12.34).toPrecision(2) // "12"
        (12.34).toPrecision(3) // "12.3"
        (12.34).toPrecision(4) // "12.34"
        (12.34).toPrecision(5) // "12.340"
      • 两种赋值方式的历史:
        var n = new Number(1)是当初被要求要像java才做出来的方式,实际上更受人喜欢的依然是var n = 1
        但是这样方式无法使用Number对象的方法怎么办,于是又有了下面的东西;

      • 基本包装类型
        理论上基本类型是不能没有toString()之类的方法的,然而实际上却可以使用,
        这是因为每当地区一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据,如下:

        1
        2
        3
        4
        5
        6
        7
        8
        var n = 1  // 1
        var s = n.toString() // '1'
        // 后台自动操作
        var temp = new Number(n) // 创建一个 Number 类型的实例
        temp.toString() // 调用指定方法
        // 然后将temp.toString()的值作为n.toString()的值
        // 最后销毁temp,释放空间
        // 所以这是一种临时的转换,基本类型实际上并没有属性,

        也因此,var n = new Number(1)这种方式基本不再被大家所使用

      • 思考题

        1
        2
        3
        4
        5
        6
        var n = 1
        n.xxx = 2
        // 问:是否会报错?
        // NO!
        n.xxx = 2 // 2
        n.xxx // undefined

        解析:n.xxx = 2这段代码执行时新建了一个临时变量temp以及属性xxx储存值2,所以不会报错,
        但是执行 n.xxx 的时候浏览器显示的却是 undefined,这是因为 temp 是临时创建出来了,使用后会立即销毁,执行 n.xxx 的时候上一句代码创建的临时temp已经被销毁了,这时候又是一个新的temp,它并没有xxx这个属性以及值

      • String()和Boolean()也是同理
      • 阮一峰
      • JavaScript高级程序设计第五章-5.6-基本包装类型

        2. String()

    • MDN
    • MDN学习教程
    • 阮一峰
    • 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
      40
      41
      42
      43
      44
      45
      var s = 'abc'
      var s2 = new String(s)

      // charAt方法返回指定位置的字符,参数是从0开始编号的位置。
      s.charAt(1) // "b"
      s.charAt(s.length - 1) // "c"
      // 这个方法完全可以用数组下标替代。
      'abc'.charAt(1) // "b"
      'abc'[1] // "b"
      // 如果参数为负数,或大于等于字符串的长度,charAt返回空字符串。
      'abc'.charAt(-1) // ""
      'abc'.charAt(3) // ""

      // charCodeAt方法返回给定位置字符的Unicode码点(十进制表示),相当于String.fromCharCode()的逆操作。
      'abc'.charCodeAt(1) // 98
      // 如果没有任何参数,charCodeAt返回首字符的Unicode码点。
      'abc'.charCodeAt() // 97
      // 如何返回 16 进制表示的Unicode码点
      'abc'.charCodeAt(0).toString(16)

      // trim方法用于去除字符串两端的空格,返回一个新字符串,不改变原字符串。
      ' hello world '.trim() // "hello world"
      // 该方法去除的不仅是空格,还包括制表符(\t、\v)、换行符(\n)和回车符(\r)。
      '\r\nabc \t'.trim() // 'abc'

      // concat方法用于连接两个字符串,返回一个新字符串,不改变原字符串。
      var s1 = 'abc';
      var s2 = 'def';
      s1.concat(s2) // "abcdef"
      s1 // "abc"
      s2 // "def"
      // 该方法可以接受多个参数。
      'a'.concat('b', 'c') // "abc"

      // slice方法用于从原字符串取出子字符串并返回,不改变原字符串。
      // 它的第一个参数是子字符串的开始位置,第二个参数是子字符串的结束位置(不含该位置)。
      'JavaScript'.slice(0, 4) // "Java"
      // 如果省略第二个参数,则表示子字符串一直到原字符串结束。
      'JavaScript'.slice(4) // "Script"
      // 如果参数是负值,表示从结尾开始倒数计算的位置,即该负值加上字符串长度。
      'JavaScript'.slice(-6) // "Script"
      'JavaScript'.slice(0, -6) // "Java"
      'JavaScript'.slice(-2, -1) // "p"
      // 如果第一个参数大于第二个参数,slice方法返回一个空字符串。
      'JavaScript'.slice(2, 1) // ""
    • String()有很多api,这里只写了几个常用的,更多详见上面两个链接

      3. Boolean()

    • MDN
    • 阮一峰
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      var b = new Boolean(true)
      b.toString() // 'true'
      b.valueOf() // true

      // 踩坑
      var f = false
      var f2 = new Boolean(false)
      if(f) { console.log(1) }
      if(f2) { console.log(2) }
      // 问:会打印出什么?
      2 // 只打印 2,因为 f2 并不是 false,因为它是一个对象,而对象是 true
      // 别忘记js中只有 NAN、null、undefined、''、0是false,而对象和其它的都是true

    4. Object()

    • MDN
    • 阮一峰
    • 1
      2
      3
      4
      5
      var o = {}
      var o2 = new Object()
      // 这两种写法的结果没有任何区别,因为 对象 本身是复杂类型,但是
      o === o2 // false
      // 这是因为它们虽然都创建一个空对象,但是地址并不一样,是两个地址不同拥有自己空间的空对象
  • 公共属性(原型)

    1
    2
    3
    4
    5
    6
    7
    var n = new Numer(1)
    var s = new String('a')
    var b = new Boolean(true)
    var o = new Object()

    // 上面4个不同的对象都有`toString()、valueOf()`属性,可是如果每个对象都创建2个属性会很浪费内存,
    // 所以,共用就好了


    那么怎么访问公共属性呢?每个对象的toString储存公共属性的toString的地址?不

    • proto
      js通过隐藏属性__proto__属性来访问公共属性,它指向了那些所有对象共有的属性


      步骤:

      1. 首先查看自身是否是对象,如果不是就包装对象
      2. 如果是对象就查看自身的 key 有没有 toString,有的话就直接调用
      3. 如果没有的话就通过__proto__查看你的共用属性看有没有,有的话就将你自身对应的值打印出来
        例子:
        1
        2
        3
        4
        5
        6
        7
        var o1 = {name: 'zero'}
        o1.toString() // [object Object]
        // 依照步骤来,o1是对象,o1没有toString,查看共有属性,有toString,打印o1对应的值
        // 不仅仅是o1,创建更多的对象也是一样,所有的对象都有隐藏属性`__proto__`,指向对应的共有属性
        var o2 = {}
        o1 === o2 // false
        o1.toString() === o2.toString() // true
    • 如果是Number对象呢?

      1
      2
      var n = new Number(11)
      n.toString(16) // numer对象可以这样,而oject对象是不能这样toString(16)的



      可以看见,Number重写了toString属性,以及添加一部分自己的属性,
      然后通过__proto__指向Object,对象共有属性
      所以如同上图,Number对象就是在第3步去Number重写的属性寻找,没有的话去共有属性找
      Number对象的__proto__指向Number重写的共有属性,Number重写的共有属性的__proto__指向对象共有属性

    • 除了Number以外,类似的还有String、Boolean(目前所学的)
    • 原型链

      • 对象的属性和方法,有可能定义在自身,也有可能定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain),就像我们上面画的图一样,
      • 如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。Object.prototype 就是 Object对象的共有属性,相对的,Number.prototype就是Number对象的共有属性,而Number.prototype的proto又指向Object.prototype,也就是它的共有属性,String和Boolean也是如此



        看这张图,结合前面所学的数据结构,是不是很有意思,
        根节点是Object.prototype,三个子节点分别是String.prototype、Number.prototype、Boolean.prototype,
        当你执行var str = new String('a')时,浏览器就会在堆中创建一个hash,str保存这个hash的地址,
        这个hash也就是String对象中除了你定义的属性外还有隐藏属性proto指向String.prototype,String.prototype又指向Object.prototype,这就完成了String API的所有绑定,
        Number和Boolean也是一样
      • “原型链”的作用是,读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
      • 如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
      • 需要注意的是,一级级向上,在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
      • prototype与proto

        两者都指向共用属性
        String.prototype 是 String 的共用属性的引用(防止共用属性被垃圾回收)
        s.proto 是 String 的共用属性的引用(这是你在用共用属性)



        proto 是对象的属性,prototype 是函数的属性
        它们储存的地址相同,指向的其实是同一个对象

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        // 一些烧脑的东西

        var obj1 = 函数.prototype
        obj1.__proto__ === Object.prototype // 等于下面
        函数.prototype.__proto__ === Object.prototype

        var obj2 = 函数
        obj2.__proto__ === Function.prototype // 也就是
        函数.__proto__ === Function.prototype
        Function.__proto__ === Function.prototype
        Function.prototype.__proto__ === Obeject.prototype

        String.__proto__ === Function.prototype
        Number.__proto__ === Function.prototype
        Boolean.__proto__ === Function.prototype
        Object.__proto__ === Function.prototype


        总之,记住公式:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        对象.__proto__ === (对象原型)函数.prototype
        函数.__proto__ === Function.prototype
        Function.prototype.__proto_ === Object.prototype
        函数.prototype.__proto_ === Object.prototype

        // 测试一下代码就可以证明
        function fc(){}

        var o = new fc()
        o.__proto__ === fc.prototype
        fc.__proto__ === Function.prototype
        fc.prototype.__proto__ === Object.prototype
        Object.prototype.__proto__ === null

        也就是说:

        • 所有对象的__proto__引用此对象原型函数的prototype属性
        • 所有函数的__proto__引用Function.protype,不管你自己写的还是 api
        • Function.protype.__proto__以及你自己写的函数的共用属性prototype__proto__引用Object.prototype

          参考
      • 那么,Object.prototype对象有没有它的原型呢?回答是有的,就是没有任何属性和方法的null对象,而null对象没有自己的原型。
      • Object.getPrototypeOf(Object.prototype) // null
        上面代码表示,Object.prototype对象的原型是null,由于null没有任何属性,所以原型链到此为止。
        Object.prototype 就是 Object对象的共有属性
      • 阮一峰