理解原型和实例的创建

来源:互联网 发布:金属探测器软件 编辑:程序博客网 时间:2024/06/06 18:21

原型 , 构造模式 , 原型模式 ,组合模式

  • 理解原型和实例的创建
    • 理解原型
    • 与原型相关的属性和方法
      • constructor 属性
      • prototype属性
      • hasOwnProperty 方法
      • in操作符
    • 两个模型构造函数模型 和 原型模型
      • 1构造函数模型
        • 11构造过程
        • 12构造函数的优势
        • 13构造函数的缺陷
      • 2原型模型
        • 21原型模式的优势
        • 14 原型模式的缺陷
      • 4 总结
    • 组合使用两种模型
      • 1 组合模式
      • 2 动态原型模式
      • 3 优化

理解原型和实例的创建

1. 理解原型

在JS中,只要声明一个函数A , 就会对应的产生一个对象 , 这个对象就叫原型对象

试试看

    function A() {    }    console.log(A.prototype);    console.log(A.prototype.constructor);

返回值如下:

定义了一个函数 A , 没有添加任何的属性和方法 , A 的两未定义个属性 却可以返回两个值?

prototype属性返回了一个对象 , constructor属性返回了函数A

prototype是 A 里面的属性 , 指向一个对象,这个对象就叫原型对象 ,

而这个原型对象里 默认情况 储存着 constructor属性和从Object继承而来,这里暂且不表;

constructor 是一个属性 , 存在原型对象里 , 指向了这个函数

比如俩人吵架 , 都用手指头指着对方骂;

而这两个属性就是手指头,构造函数和原型对象就是吵架的人;

手指头是人的一部分,就像这两个属性存在对象的内部是一样的

2.与原型相关的属性和方法

constructor 属性

存在于原型对象中,指向构造函数;

如果重写原型对象则会导致constructor属性不再指向构造函数;

如果需要constructor依旧指向构造函数,可以在重写的时候加上以下代码

Person.prototype = {       constructor : Person    //让constructor重新指向Person函数}

[[prototype]]属性

用构造函数创建一个实例对象后,这个对象中会有一个不可访问的属性[[prototype]],这个属性就指向了构造函数的原型对象

Chrome浏览器和火狐浏览器提供这个对这个属性的访问

使用__proto__方法可以访问到原型对象(左右各两个下划线),这个属性存在于被构造函数创建的实例对象中;一般不建议使用

hasOwnProperty() 方法

判断一个属性是否来自对象自身 ;

返回 true表示为对象自身属性;返回false可以判断出存在原型中或属性不存在,所以要如何确定一个属性存在原型里呢?

in操作符

in操作符会从对象的本身开始 , 查找是否有对应的属性 , 在对象自身中没有找到 , 就会沿着原型链开始查找 , 直到找到返回true ,反之,则返回false

3. 两个模型–构造函数模型 和 原型模型

3.1构造函数模型

理论上任何函数都可以作为构造函数,但是一般约定,构造函数的函数名以大写字母开头

比如:

构造函数 : function Person(){ }

当把一个函数作为构造函数,并利用new创建的一个新的实例对象

利用构造函数创建实例 : var p1 = new Person( );

3.1.1构造过程

相关:

实例与构造函数的关系 :

实例对象p1被构造函数Person创建后,P1和Person是没有任何的关系了;

实例与原型 :

  • 实例对象中有一个[[prototype]]属性指向原型对象;
  • 使用new Person()创建多个对象,则多个对象都会同时指向原型对象。
  • 可以手动给这个原型对象添加属性和方法,那么p1,p2,p3…都会共享这些属性和方法

  • 属性和方法的查找会从实例开始 , 沿着原型链查找 , 直到找到属性或方法;

  • 所以给实例对象添加和原型中同名的属性 , 会优先访问实例中的属性
  • 通过实例只能访问到原型的属性或方法,不能修改;
//这段代码将会抛出错误function Person(name,age) {    }    Person.prototype.sex = "男"    let p1 = new Person();        p1.prototype.sex = "女";    console.log(p1.sex);

3.1.2构造函数的优势

可以传入参数,适用于 每个实例都有的同名但是值不相同的属性

     function Person(name,age) {         this.name = name;         this.age = age;         this.speakName = function (){            console.log(this.name)         }     }     let p1 = new Person("shark",3);     let p2 = new Person("dd",4)     console.log(p1.name,p1);     console.log(p1.age)     //输出结果     shark 3     dd 4

内存模型:

目前来说,每个实例都有了自己的name和age , 但是新的问题也随之而来了


3.1.3构造函数的缺陷

观察内存模型 , 就会发现每个实例中都有一个相同的方法 , 浪费了内存

这不是很OK , 不是我们想要的结果,所以一种新的模型站了出来 - -原型模型

3.2原型模型

3.2.1原型模式的优势

针对以上的问题 , 对代码做出了以下修改

     function Person(name,age) {         this.name = name;         this.age = age;     }     Person.prototype.speakName = function (){        console.log(this.name)      }     let p1 = new Person("shark",3);     let p2 = new Person("dd",4)     console.log(p1.name,p1);     console.log(p1.age)

利用原型模式的优势 —共享所有的方法

可以让我们共享speakName这个方法;

这正好解决了构造函数的缺陷 , 每个实例都有自己的名字 , 方法在不浪费内存和性能的情况下共用;


3.14 原型模式的缺陷

那假如使用原型模式添加属性呢?

    function Person() {    }    Person.prototype.country = "China";    Person.prototype.name = "小李";    var p1 = new Person();    var p2 = new Person();

场景:

现在有假如两个中国人p1,p2 ,他们的国籍一样,使用原型模式,使得两个人的国籍一致

结合上一个例子说明了:

原型中适合存储大家共有的属性和方法;

不过原型也存在缺陷 :

名字没有办法做到每个人都独一无二 , 不过在之前提到的构造函数模型中 , 正好解决了给不同的实例的相同name赋不同值的情况;

3.4 总结

原型模式适合封装方法和共享的属性,构造方法模式适合封装值不同的属性

如果把这两个模式结合起来,就有了组合模式;

4.组合使用两种模型

组合构造是基于两种模式互补的一种新的构造方法;

总结一下两种模式的优缺点:

优势 缺陷 构造函数模式 属性在对象中都独有一份 原型模式 方法可以共享

不难看出,两者的正好是互补的,所以组合起来使用是最佳的方法;

4.1 组合模式

    //在构造方法内部封装属性    function Person(name, age) {        this.name = name;        this.age = age;    }    //在原型对象内封装方法    Person.prototype.eat = function (food) {        alert(this.name + "爱吃" + food);    }    Person.prototype.play = function (playName) {        alert(this.name + "爱玩" + playName);    }    var p1 = new Person("李四", 20);    var p2 = new Person("张三", 30);    p1.eat("苹果");    p2.eat("香蕉");    p1.play("志玲");    p2.play("凤姐");

虽然完美解决了种模式的缺陷,但是还不够完美

因为 , 代码还不够优雅

4.2 动态原型模式

动态原型模式把所有的属性和方法都封装在构造方法中,而仅仅在需要的时候才去在构造方法中初始化原型,又保持了同时使用构造函数和原型的优点。

<script type="text/javascript">    //构造方法内部封装属性    function Person(name, age) {        //每个对象都添加自己的属性        this.name = name;        this.age = age;        /*            判断this.eat这个属性是不是function,如果不是function则证明是第一次创建对象,            则把这个funcion添加到原型中。            如果是function,则代表原型中已经有了这个方法,则不需要再添加。            perfect!完美解决了性能和代码的封装问题。        */        if(typeof this.eat !== "function"){            Person.prototype.eat = function () {                alert(this.name + " 在吃");            }        }    }    var p1 = new Person("志玲", 40);    p1.eat();    </script>

看起来优美多了..但是还是差一丝优美…

4.3 优化

    function Person(opt) {         this._init(opt)        }    //方法和属性都被封装到了一起,但是属性实际上还是属于构造模式创建的    //体现了封装性,但是重写Prototype带来了一个小问题    Person.prototype = {      //新的原型对象不存在constructor属性,故补齐         constructor:Person       //初始化属性        _init: function (opt) {            this.name = opt.name;            this.age = opt.age;        },        eat: function () {            return "名字:" + this.name        },        howOld: function () {            return "年龄:" + this.age        },    };    var p1 = new Person({        name: "李四",        age: 99    });

前端新人一枚 , 欢迎批评指正~

原创粉丝点击