原型2 5.创建对象的方式_寄生构造函数与稳妥构造函数 寄生构造函数:其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使用其他模式时,不建议使用此模式。

1.为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的声明可以使用字面量的方式

使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区别,字面量创建的方式使用 constructor 属性不会指向实例,而会指向 Object,构造函数创建的方式则相反。

    function Box() {};
        Box.prototype = {             //使用字面量的方式创建原型对象,这里{}就是对象,是Object,new Object就相当于{}
        name : 'Lee',
        age : 100,
        run : function () {
            return this.name + this.age + '运行中...';
        }
    };
    var box = new Box();
    alert(box instanceof Box);
    alert(box instanceof Object);
    alert(box.constructor == Box);             //字面量方式,返回 false,否则,true
    alert(box.constructor == Object);         //字面量方式,返回 true,否则,false

如果想让字面量方式的 constructor 指向实例对象,那么可以这么做:定义的时候加上constructor : Box,

    function Box() {};
        Box.prototype = {             //使用字面量的方式创建原型对象,这里{}就是对象,是Object,new Object就相当于{}
        constructor : Box,             //直接强制指向即可
        name : 'Lee',
        age : 100,
        run : function () {
            return this.name + this.age + '运行中...';
        }
    };
    var box = new Box();
    alert(box.age);
    alert(box instanceof Box);
    alert(box instanceof Object);
    alert(box.constructor == Box);             //true
    alert(box.constructor == Object);         //false

PS:字面量方式为什么 constructor 会指向 Object?因为 Box.prototype={};这种写法其实就是创建了一个新对象。而每创建一个函数,就会同时创建它 prototype,这个对象也会自动获取 constructor 属性。所以,新对象的 constructor 重写了 Box 原来的 constructor,因此会指向新对象,那个新对象没有指定构造函数,那么就默认为 Object。



 2.原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。

    function Box() {};
        Box.prototype = {             //使用字面量的方式创建原型对象,这里{}就是对象,是Object,new Object就相当于{}
        constructor : Box,             //直接强制指向即可
        name : 'Lee',
        age : 100,
        run : function () {
            return this.name + this.age + '运行中...';
        }
    };
    //重写原型对象
    Box.prototype = {
        name : 'cary',                    //这里不会保留之前原型的任何信息了,把原来的原型对象和构造函数对象实例之间的关系切断了
    }
    var box = new Box();
    alert(box.age);//结果Undefined

3.原型对象不仅仅可以在自定义对象的情况下使用,而 ECMAScript 内置的引用类型都可以使用这种方式,并且内置的引用类型本身也使用了原型。

alert(Array.prototype.sort);         //sort 就是 Array 类型的原型方法
alert(String.prototype.substring); //substring 就是 String 类型的原型方法

可以给这写引用类型添加自定义的函数:

  String.prototype.addstring = function () {             //给 String 类型添加一个方法
        return this + ',被添加了!';                     //this 代表调用的字符串
    };
    alert('Lee'.addstring());                             //使用这个方法

尽管给原生的内置引用类型添加方法使用起来特别方便,但我们不推荐使用这种方法。因为它可能会导致命名冲突,不利于代码维护。

4.原型的缺点:

原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:属性共享,而且不能传参

  function Box() {};
    Box.prototype = {
        constructor : Box,
        name : 'Lee',
        age : 100,
        family : ['父亲', '母亲', '妹妹'],             //添加了一个数组属性
        run : function () {
            return this.name + this.age + this.family;
        }
    };
    var box1 = new Box();
    alert(box1.family);
    box1.family.push('哥哥');         //在实例中添加'哥哥'
    alert(box1.run());
    
    var box2 = new Box();
    alert(box2.run()); //共享带来的麻烦,也有'哥哥'了

数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化出的数据需要保留自己的特性,而不能共享。为了解决构造传参和共享问题,可以组合构造函数 +原型模式

  function Box(name, age) { //不共享的属性使用构造函数
        this.name = name;
        this.age = age;
        this. family = ['父亲', '母亲', '妹妹'];
    };
    Box.prototype = { //共享的属性或方法使用原型模式
        constructor : Box,
        run : function () {
            return this.name + this.age + this.family;
        }
    };
    var box1 = new Box();
    alert(box1.family);
    box1.family.push('哥哥');         //在实例中添加'哥哥'
    alert(box1.family);
    
    var box2 = new Box();
    alert(box2.family);                    //引用类型没有使用原型,所以没有共享

这种混合模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法

原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在声明一个对象时,构造函数+原型部分让人感觉又很怪异,最好就是把构造函数和原型封装到一起。为了解决这个问题,我们可以使用动态原型模式。

function Box(name ,age) {                             //将所有信息封装到函数体内
        this.name = name;
        this.age = age;
        
        //判断this.run是否存在
        if (typeof this.run != 'function') {             //原型的初始化,只要第一次调用初始化就可以了,没必要每次构造函数实例化的时候都初始化
                                                        //加上判断即可仅在第一次调用的初始化
            Box.prototype.run = function () {
                return this.name + this.age + '运行中...';
            };
        }
    }
    var box = new Box('Lee', 100);
    alert(box.run());

当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会再初始化了。这样及得到了封装,又实现了原型方法共享,并且属性都保持独立。

function Box(name ,age) {                             //将所有信息封装到函数体内
        this.name = name;
        this.age = age;
        if (typeof this.run != 'function') {             //仅在第一次调用的初始化
            alert('第一次初始化');                     //测试用
            Box.prototype.run = function () {
                return this.name + this.age + '运行中...';
            };
        }
    }
    
    var box1 = new Box('Lee', 100); //第一次创建对象
    alert(box1.run()); //第一次调用
    alert(box1.run()); //第二次调用
    
    var box2 = new Box('Jack', 200); //第二次创建对象
    alert(box2.run());
    alert(box2.run());

使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系。

寄生构造函数:其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使用其他模式时,不建议使用此模式。

function Box(name, age) {
        var obj = new Object();
        obj.name = name;
        obj.age = age;
        obj.run = function () {
            return this.name + this.age + '运行中...';
        };
        return obj;
    }
    var box1 = new Box('Lee',21);
    alert(box1.run());
    
    var box2 = new Box('LLL',25);
    alert(box2.run());

在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议直接 String.prototype.addstring,可以通过寄生构造的方式添加。

function myString(string) {
        var str = new String(string);
        str.addstring = function () {
            return this + ',被添加了!';
        };
        return str;
    }
    var box = new myString('Lee'); //比直接在引用原型添加要繁琐好多
    alert(box.addstring());

 稳妥构造函数:在一些安全的环境中,比如禁止使用 this 和 new,这里的 this 是构造函数里不使用 this,这里的 new 是在外部实例化构造函数时不使用 new。

function Box(name , age) {
        var obj = new Object();
        obj.run = function () {
            return name + age + '运行中...'; //直接打印参数即可
        };
        return obj;
    }
    var box = Box('Lee', 100); //直接调用函数
    alert(box.run());