JavaScript学习——对象

1. 本文是在学习廖雪峰先生的JavaScrip教程 后的归纳

一、标准对象

  1. typeof
    • JavaScript的世界,一切都是对象
    • typeof操作符获取对象的类型,总是返回一个字符串
    typeof 123; // 'number'
    typeof NaN; // 'number'
    typeof 'str'; // 'string'
    typeof true; // 'boolean'
    typeof undefined; // 'undefined'
    typeof Math.abs; // 'function'
    typeof null; // 'object'
    typeof []; // 'object'
    typeof {}; // 'object'
    
    • numberstringbooleanfunctionundefined有别于其他类型
    • nullArray{}的类型是object,故typeof无法区分三者
  2. 包装对象
    • numberboolean、和string都有包装类型,使用new创建
    var n = new Number(123); // 123,生成了新的包装类型
    var b = new Boolean(true); // true,生成了新的包装类型
    var s = new String('str'); // 'str',生成了新的包装类型
    
    • 包装对象虽和原来的值一样,但类型已经发生变化,都变成object
    • 尽量不要使用包装类型,尤其是string类型
    • NubmerBooleanString没写new时,可以被当成普通函数,把任何类型的数据转换为numberbooleanstring类型(不是包装类型)
    var n = Number('123'); // 123,相当于parseInt()或parseFloat()
    typeof n; // 'number'
    
    var b = Boolean('true'); // true
    typeof b; // 'boolean'
    
    var b2 = Boolean('false'); // true! 'false'字符串转换结果为true!因为它是非空字符串!
    var b3 = Boolean(''); // false
    
    var s = String(123.45); // '123.45'
    typeof s; // 'string'
    
  3. 总结
    • 不要使用new Number()、new Boolean()、new String()创建包装对象;
    • parseInt()parseFloat()来转换任意类型到number
    • String()来转换任意类型到string,或者直接调用某个对象的toString()方法;
    • 通常不必把任意类型转换为boolean再判断,因为可以直接写if (myVar) {...}
    • typeof操作符可以判断出number、boolean、string、function和undefined
    • 判断Array要使用Array.isArray(arr)
    • 判断null请使用myVar === null
    • 判断某个全局变量是否存在用typeof window.myVar === 'undefined'
    • 函数内部判断某个变量是否存在用typeof myVar === 'undefined'
    • nullundefined 没有toString()方法
    • 特殊情况:number使用toString()方法
    123..toString();//两个点
    (123).toString();
    

二、Date

  1. Date表示日期和时间
    • 获取系统时间如下:
    var now = new Date();
    now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
    now.getFullYear(); // 2015, 年份
    now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月
    now.getDate(); // 24, 表示24号
    now.getDay(); // 3, 表示星期三
    now.getHours(); // 19, 24小时制
    now.getMinutes(); // 49, 分钟
    now.getSeconds(); // 22, 秒
    now.getMilliseconds(); // 875, 毫秒数
    now.getTime(); // 1435146562875, 以number形式表示的时间戳
    
    • 创建一个指定日期和时间的Date对象,可以用:
    var d = new Date(2015, 5, 19, 20, 15, 30, 123);
    d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST)
    
    • JavaScript的月份范围用整数表示是0-11
    • 创建一个指定日期和时间的方法是解析一个符合ISO 8601格式的字符串
    • Data.parse('2015-06-24T19:49:22.875+08:00'); 返回一个时间戳;该时间戳很容易转换成Date
  2. 时区
    • Date对象表示的时间总是按浏览器所在时区显示的,不过,我们既可以显示本地时间,也可以显示调整后的UTC时间
    var d = new Date(1435146562875);
    d.toLocaleString(); // '2015/6/24 下午7:49:22',本地时间(北京时区+8:00),显示的字符串与操作系统设定的格式有关
    d.toUTCString(); // 'Wed, 24 Jun 2015 11:49:22 GMT',UTC时间,与本地时间相差8小时
    
    • 时间戳: 是一个自增的整数,它表示从1970年1月1日零时整的GMT时区开始的那一刻,到现在的毫秒数。

三、RegExp

  1. 基本
    • \d可以匹配一个数字,\w可以匹配一个字母或数字
    • .可以匹配任意字符,*表示任意个字符,用+表示至少一个字符,?表示0个或1个字符,用{n}表示n个字符,用`{n,m}表示n-m个字符
    • \s可以匹配一个字符(也包括Tab等字符)
  2. 进阶
    • []表示范围,A|B可以匹配A或B
    • ^ 表示行的开头,^\d表示必须以数字开头
    • & 表示行的结束
  3. JavaScript中使用正则表达式
    • 写法一: /正则表达式/
    • 写法二: new RegExp('正则表达式)`创建一个RegExp对象
    • 示例:
      var re = /^\d{3}\=\d{3,8}$/;
      re.test('010-12345');//true
      
    • RegExp对象test()方法用于测试给定的字符串是否符合规定
    • 切分字符串:用正则表达式来把不规范的输入转化成正确的数组
    • 分组:提取子串的功能,通过()表示的就是要提取的分组(Group)
      var re = /^(\d{3})-(\d{3,8})$/;
      re.exec('010-12345'); // ['010-12345', '010', '12345']
      re.exec('010 12345'); // null
      
      在正则表示式中定义了组,就可以在RegExp对象上用exec()方法提取出子串来
      exec()方法在匹配成功后,会返回一个Array,第一元素始终是原始字符串本身,后面的字符串表示匹配成功的子串
      exec()方法在匹配失败后,返回null
    • 正则表达式默认的是贪婪匹配,也就是匹配尽可能多的字符
    • \d+加个?就可以采用贪婪匹配
  4. 全局搜索
    • g表示全局匹配
    • var r1 = /test/g; 等价于 var r2=new RegExp('test','g');
    • 全局匹配可以多次执行exec()方法来搜索一个匹配的字符串,当指定g标志后,每次运行exec(),正则表达式本身会更新lastIndex属性,表示上次匹配到的最后索引
    var s = 'JavaScript, VBScript, JScript and ECMAScript';
    var re=/[a-zA-Z]+Script/g;
    
    // 使用全局匹配:
    re.exec(s); // ['JavaScript']
    re.lastIndex; // 10
    
    re.exec(s); // ['VBScript']
    re.lastIndex; // 20
    
    re.exec(s); // ['JScript']
    re.lastIndex; // 29
    
    re.exec(s); // ['ECMAScript']
    re.lastIndex; // 44
    
    re.exec(s); // null,直到结束仍没有匹配到
    
    • 全局匹配类似搜索,因此,不能使用/^...$/,那样只会最多匹配一次
    • 正则表达式还可以指定i标志,表示忽略大小写,m标志,表示执行多行匹配

四、JSON

  1. 定义
    • JSON是JavaScript Object Notation的缩写,是一种数据交换格式
    • 由道格拉斯·克罗克福特(Douglas Crockford)发明
    • JSON实际上是JavaScript的一个子集
    • JSON数据类型和JavaScript基本一样: number,boolean,string,null,array,object以及前面的任意组合
    • JSON定死了字符集为UTF-8,对多语言没有问题
    • 为了统一解析,JSON的字符串规定必须用"",Object的键也必须用双引号""
    • 把任何JavaScript对象变成JSON,就是把这个对象序列化成一个JSON格式的字符串
    • 收到一个JSON格式的字符串,只需要把它反序列化成一个JavaScript对象
  2. 序列化
    • 示例:
    var xiaoming = {
        name: '小明',
        age: 14,
        gender: true,
        height: 1.65,
        grade: null,
        'middle-school': '\"W3C\" Middle School',
        skills: ['JavaScript', 'Java', 'Python', 'Lisp']
    };
    
    JSON.stringify(xiaoming); // '{"name":"小明","age":14,"gender":true,"height":1.65,"grade":null,"middle-school":"\"W3C\" Middle School","skills":["JavaScript","Java","Python","Lisp"]}'
    
    • 输出比较规范: JSON.stringify(xiaoming, null, ' ');
    • JSON.stringify(xiaoming,['name','skill'],' ');
      第二个参数用来筛选对象的键值,只输出指定的属性,则Array
    • JSON.stringify(xiaoming, convert, ' ');还可以传入一个函数(function convert(key,value){})
    • 还可以为对象定义一toJSON()方法
    var xiaoming = {
        name: '小明',
        age: 14,
        gender: true,
        height: 1.65,
        grade: null,
        'middle-school': '\"W3C\" Middle School',
        skills: ['JavaScript', 'Java', 'Python', 'Lisp'],
        toJSON: function () {
            return { // 只输出name和age,并且改变了key:
                'Name': this.name,
                'Age': this.age
            };
        }
    };
    
    JSON.stringify(xiaoming); // '{"Name":"小明","Age":14}'
    
  3. 反序列化
    • 拿到一JSON格式的字符串,我们直接用JSON.parse()把它变成一个JavaScript对象
    • 示例:
    JSON.parse('[1,2,3,true]'); // [1, 2, 3, true]
    JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}
    JSON.parse('true'); // true
    JSON.parse('123.45'); // 123.45
    
    • JSON.parse()可以接收一个函数,用来转换解析出来的属性
    • 示例:
    JSON.parse('{"name":"小明","age":14}', function (key, value) {
         // 把number * 2:
         if (key === 'name') {
             return value + '同学';
         }
         return value;
    });// Object {name: '小明同学', age: 14}
    

五、面向对象编程

  1. 面向对象
    • 类和实例是大多数面向对象编程的基本概念
    • JavaScript中,JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程
    • 示例:
    var Student = {
        name: 'Robot',
        height: 1.2,
        run: function () {
            console.log(this.name + ' is running...');
        }
    };
    
    var xiaoming = {
        name: '小明'
    };
    
    xiaoming.__proto__ = Student;
    
    • JavaScript的原型链和Java的Class的区别就是在于,JavaScript没有"Class"概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已
    • 编写JavaScript代码时,不要直接用obj.__proto__去改变一个对象的原型,可以使用Object.create()方法可以传入一个原型对象,并创建一个基于该原型的新对象,但新对象什么属性都没有
    • 示例:
    function createStudent(name) {
        // 基于Student原型创建一个新对象:
        var s = Object.create(Student);
        // 初始化新对象:
        s.name = name;
        return s;
    }
    

六、创建对象

  1. 原型链
    • JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象
    • 当使用obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没找到,就到Object.prototype对象,最后还没找到,就只能返回undefined
      示例:
    var arr=[1,2,3];
    原型链是:
        arr---->Array.prototype-->Object.prototype--->null
    其中`Array.prototype`定义了`indexOf()`、`shift()`等方法,
     因此可以在所有`Array`对象上直接调用执行方法
    
    function foo(){
        return 0;
    }
    foo--->Function.prototype--->Object.prototype--->null
    其中Function.prototype定义了apply()方法
    
    • 如果原型链很长,那么访问一个对象的属性会因为耗费更多时间查找而变得更慢,因此,原型链不宜太长
  2. 构造函数
    • 除了{...}直接创建对象外,还可以通过构造函数的方法来创建对象
    • 示例:
    function Student(name) {
        this.name = name;
        this.hello = function () {
            alert('Hello, ' + this.name + '!');
        }
    }   
    
    var xiaoming=new Student('xiaoming');
    原型链如下:
    xiaoming---->Student.prototype--->Object.prototype--->null
    
    • 在JavaScript中,可以用关键字new来调用这个函数,并返回一个对象
    • 不写new,是个普通函数,加上new就变成一个构造函数,它绑定this指向新创建的对象,并默认返回this,即不需要在最后加return this
    • new Student()创建的对象还从原型上获得了一constructor属性,其指向Student本身
    xiaoming.constructor === Student.prototype.constructor; // true
    Student.prototype.constructor === Student; // true
    Object.getPrototypeOf(xiaoming) === Student.prototype; // true
    xiaoming instanceof Student; // true
    
    • 让创建对象共享一个函数时,根据对象的属性查找原则,可以把函数移到对象共同的原型上
    • 为了区分普通函数和构造函数,按照约定,构造函数首字母大写,而普通函数首字母小写
    • 一个常用的编程模式:
    function Student(props) {
        this.name = props.name || '匿名'; // 默认值为'匿名'
        this.grade = props.grade || 1; // 默认值为1
    }
    Student.prototype.hello = function () {
        alert('Hello, ' + this.name + '!');
    };
    function createStudent(props) {
        return new Student(props || {})
    }
    var xiaoming = createStudent({
        name: '小明'
    });
    
    xiaoming.grade; // 1
    优点: 这个`createStudent()`函数有几个优点:
        a. 不需要new来调用
        b. 参数非常灵活,可以传参,也可以不传参
    

七、原型继承

  1. 继承
    • Java等传统的基于Class语言,本质上是扩展一个已有的Class,并生成新的Subclass,由于这类语言严格区分类和实例,继承实际上是类型的扩展
    • JavaScript 采用的原型继承,无法直接扩展一个Class
    • JavaScript的原型继承实现方式就是:
      • 定义新的构造函数,并在内部用call()调用希望"继承"的构造函数,并绑定this
      • 借助中间函数F实现原型链继承,最好通过封装的extendes函数完成
      • 继续在新的构造函数的原型上定义新方法

推荐阅读更多精彩内容