前言

本文主要讲解一下JS中一个相对重要的概念:对象 Object!

除此之外,还会涉及工厂方法创建对象、构造函数、this、原型 prototype 和语法糖 Class

 

 

对象

 

概念

在讲解对象 Object 之前,就不得不提及一下编程语言的思想:

  • 面向过程编程 POP
  • 面向对象编程 OOP

 

篇幅有限,这里只做简单介绍:

POP 方式其实很好理解,就是先分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候依次调用,顺序执行。

OOP 则是一种更符合人类自然观的编程思想。针对业务处理过程的实体及其属性和行为进行抽象封装。

 

OOP有三个特征:封装,继承,多态。

在 JavaScript 中,对象是一组无序的相关属性和方法的集合。

对象的作用是:封装信息。比如Student类里可以封装学生的姓名、年龄、成绩等。

对象具有特征(属性)和行为(方法)。

 

为什么需要对象

一般时候,当我们需要保存单个信息或数值时,可以简单的通过变量来实现;如果一组或某些信息时,则可以透过数组 [Array] 完成:

    var arr = ['王二', 35, '男', '180'];

 

然而,这种表达方式比较乱。而如果用JS中的对象来表达,结构会更清晰。如下:

    var person = {};

    person.name = '王二';
    person.age = 35;
    person.sex = '男';
    person.height = '180';

由此可见,对象里面的属性均是键值对:

  • 键:相当于属性名。
  • 值:相当于属性值,可以是任意类型的值(number、string、boolean,function(称为方法),甚至object (fun is object too) 等)

 

创建对象

创建对象的方式有许多种,例如使用字面量创建:

    var person1 = {};
    person1.name = 'jack';
    person1.age = '38';

    var person2 = {};
    person2.name = 'tom';
    person2.age = '45';

 

这里主要讲解三种:

1、工厂方式创建

由上面的例子可看出,使用字面量的方式创建对象的过程很简单,那我们为什么还需要其他方式呢? 原因在于字面量的方式简单是简单,但却很繁琐。在例子中,我们想创建两个person,要分别写两次几乎相同代码,这还不算什么,但如果业务需求是创建100个,1000个person呢?!这样就显得十分“愚蠢”了。而工厂方式则可以大批量的创建对象,从而完美解决问题。

举例:

    function createPerson(name, age){
        //创建一个新对象
        var obj = new Object();

        //向对象添加属性和方法
        obj.name = name;
        obj.age = age;
        obj.sayName = function(){
            alert(this.name);
        };

        return obj;
    }

    //创建一个对象实例
    var person1 = createPerson("jack", 38);
    var person2 = createPerson("tom", 45);

    console.log(person1); //打印结果:Object{name='jack', age=38...}
    console.log(person2); //打印结果:Object{name='tom', age=45...}

如此一来,想创建几个就创建几个,简单省事!

 

缺陷: 但即使如此,工厂方法还是有美中不足的地方,就是它实际上是通过new Object()的方式来构造的,因此不论你创建的对象是“Person”,亦或是“Dog”,它永远只返回Object给我们(也就是最后两行的打印结果:Object...)。这样我们就无法区分哪个是Person对象,哪个是Dog对象了。 要想解决此问题,那就要引入下面这个方式了。

 

2、构造函数

  • 构造函数专门用来创建不同类型的对象
  • 就是一普通的函数,创建方式和普通函数没区别
  • 不同在于
    • 构造函数其首字母习惯性大写
    • 调用时,普通函数是直接调用,而它需要用new关键字来调用!

举例:

    // 创建一个构造函数,专门用来创建Person对象
    function Person(name, age, gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.sayName = function () {
            alert(this.name);
        };
    }

    var per = new Person('孙悟空', 18, '男');
    var per2 = new Person('玉兔精', 16, '女');
    var per3 = new Person('奔波霸', 38, '男');

    // 创建一个构造函数,专门用来创建 Dog 对象
    function Dog() {}
    var dog = new Dog();

    console.log(per); //打印结果 Person{...}
    console.log(dog); //打印结果 Dog{...}

这样,就能很好的区分开不同的对象类型了。

 

Remarks:

一、new 一个构造函数的执行流程 new 在执行时,会做下面这四件事:

  1. 开辟内存空间(堆内存),在内存中创建一个新的空对象。
  2. this 指向这个新的对象。
  3. 执行构造函数里面的代码,给这个新对象添加属性和方法
  4. 返回这个新对象(所以构造函数里面不需要 return

 

二、this 的指向也有所不同:

  1. 函数的形式调用时,this 永远都是 window。比如fun();相当于window.fun();
  2. 方法的形式调用时,this 是调用方法的那个对象;如obj.sayname,this指向obj
  3. 以构造函数的形式调用时,this 是新创建的实例对象
  4. 以事件绑定函数的形式调用时,this 指向绑定事件的对象
  5. 使用 call()、apply() 和 bind() 调用时,this的指向为:传入到call() 或 apply() 中的那个对象!

 

原型对象 Prototype

在上面的代码中,我们可以看到,Person对象中有一个 sayName 的方法。这个方法实在构造函数内部创建的,也就是说,构造函数没执行一次(创建一个新的对象实例),就会新建一个新的 sayName 方法,假设需要执行10000次,便要创建10000个一模一样的新方法,而这是完全没必要的,只会耗用宝贵的内存的运算资源。

 

有两个方式可以解决:

  • 将其单独拎出来,放到全局中(虽解决了耗内存)==>但问题:污染全局域的命名空间!
  • 就是使用原型!!(推荐)==>只创建一次,不耗内存、不污染全局

举例:

    Person.prototype.sayName = function(){
        alert("Hello大家好,我是:"+this.name);
        return this.name;
    };

 

原型概念:

知识点1: 我们所创建的每一个函数,解析器都会向函数中添加一个属性 prototype。 这个属性对应着一个对象,这个对象就是我们所谓的原型对象

如果函数作为普通函数调用prototype没有任何作用, 当函数以构造函数的形式调用时,它所创建的实例对象中都会有一个隐含的属性,指向该构造函数的原型,我们可以通过proto来访问该属性。

举例:

    // 定义构造函数
    function Person() {}

    var per1 = new Person();
    var per2 = new Person();

    console.log(Person.prototype); // 打印结果:[object object]

    console.log(per1.__proto__ == Person.prototype); // 打印结果:true;  这就说明实例的原型属性__proto__ 指向构造函数的原型属性对应着的原型对象(为同一个!)。

上方代码的最后一行:打印结果表明,实例proto 和 构造函数.prototype都指的是原型对象。如下图:

 

知识点2: 原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中

以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样就不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。

 

知识点3: 使用 in 检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true。

可以使用对象的 hasOwnProperty() 来检查对象自身中是否含有该属性。

 
原型链

原型对象也是对象,所以它也有原型,当我们使用或访问一个对象的属性或方法时:

  • 它会先在对象自身中寻找,如果有则直接使用;
  • 如果没有则会去原型对象中寻找,如果找到则直接使用;
  • 如果没有则去原型的原型中寻找,直到找到Object对象的原型。
  • Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回 null

举例:

    console.log(per1.__proto__.__proto__); //去原型的原型中找

 

3、语法糖class 创建类和对象

class 语法糖是ES6的语法,目的是让我们以更熟悉的方式创建对象(至少对于先学Python的我来说,的确是这样的)

要用 class 实现构造函数创建的功能,代码如下:

    // 类中的this指向创建的实例
    class Person {
        constructor (name, age) {
            this.name = name
            this.age = age
        }

        getName () {
            return this.name
        }
    }

    // 构造一个实例
    let huhaha = new Person('huhaha', 21)
    // 在实例上调用该方法
    const myName = huhaha.getName()

    console.log(myname) // huhaha

class 是创建了一个类,在 constructor 中放的是每个通过该类构造出来的实例都可以获得属性,在 constructor 外面放的是可以获得的方法

 

详请关于class静态属性/方法、继承等,可参考:

详解es6中的class语法糖 

延伸阅读
  1. 上一篇: JS笔记 | 4. 作用域、变量提升、闭包
  2. 下一篇: JS笔记 | 2. 运算符的各注意事项

发表评论

您尚未登录,登录后方可评论~~
登陆 or 注册

评论列表

暂无评论哦~~