JavaScript 面向对象与原型(2)

如何判断属性是在构造函数的实例里,还是在原型里? 可以用hasOwnProperty()函数来验证;
console.log(box.hasOwnProperty('name')); // 实例里若有返回true,否则返回false;
in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在与实例中还是原型中;
console.log('name' in box); // =>true,存在实例中或原型中;3.更简单的原型语法(原型+字面量模式)

3.更简单的原型语法(原型+字面量模式)

function Box(){}; Box.prototype = { // 以字面量形式创建包含属性和方法的新对象; name:'Lee', age:100, run:function(){ return this.name+this.age+'运行中...'; } }; // 使用构造函数创建原型对象和使用字面量创建原型对象在使用上基本相同; // 但是,使用字面量创建的原型对象使用constructor属性不会指向实例,而是指向原型对象Object;构造函数的方式则相反; var box = new Box(); console.log(box instanceof Box); console.log(box instanceof Object); console.log(box.constructor == Box); // 字面量方式,返回false; console.log(box.constructor == Object); // 字面量方式,返回true; // 如果想让字面量方式的constructor指向实例对象: Box.prototype = { constructor:Box, // 直接强制指向即可; } // PS:字面量方式为什么constructor会指向Object? // 因为Box.prototype={}这种字面量写法就是创建一个新对象; // 而每创建一个函数,就会同时创建它的prototype,这个对象也会自动获取constructor属性; // 所以,新对象的constructor重写了Box原来的constructor,因此指向了新对象, // 那个新对象没有指定构造函数,那么就默认为是Object;

4.原型的动态性(重写会覆盖之前的内容)

// 原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型; function Box(){}; Box.prototype = { constructor:Box, name:'Lee', age:100, run:function(){ return this.age+'运行中...'; } }; Box.prototype = { // 原型重写了,覆盖了之前的原型; age:200, run:function(){ return this.age+'运行中...'; } } var box = new Box(); console.log(box.run()); // =>200运行中...; // 重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;对象实例引用的仍然是最初的原型;

5.原生对象的原型

// 原型对象不仅仅可以在自定义对象的情况下使用,而是ECMAScript内置的引用类型都可以使用这种方式,
// 并且内置的引用类型本身也是用了原型;
console.log(Array.prototype.sort); // =>function sort() { [native code] };
console.log(String.prototype.substring); // =>function substring() { [native code] };

6.原型对象的问题

// 原型模式创建对象缺点:省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的; // 而原型最大的有点就是共享,属性共享; // 但是,如果原型中的属性包含引用类型(对象),共享就会存在一定问题; function Box(){}; Box.prototype = { constructor:Box, name:'Lee', age:100, family:['father','mother'], run:function(){ return this.name+this.age+this.family; } }; var box1 = new Box(); box1.family.push('sister'); // 为box1的family属性添加了sister;而这个属性被共享到原型了; console.log(box1.run()); // =>Lee100father,mother,sister; var box2 = new Box(); console.log(box2.run()); // =>Lee100father,mother,sister; // 数据共享导致实例化出的数据不能保存自己的特性;

7.组合使用构造函数模式(对象不共享的数据)和原型模式(对象共享的数据)

// 为了解决构造传参和共享问题,组合构造函数+原型模式: function Box(name,age){ // 不共享的使用构造函数; this.name = name; this.age = age; this.family = ['father','moter']; }; Box.prototype = { // 共享的使用原型模式; constructor:Box, run:function(){ return this.name+this.age+this.family; } }; // PS:这种混合模式很好的解决了传参和引用共享的大难题;是创建对象比较好的方法;

8.动态原型模式(将原型封装到构造函数里)

// 原型模式,不管是否调用了原型中的共享方法,它都会初始化原型中的方法; // 并且在声明一个对象时,构造函数+原型让人感觉怪异;最好把构造函数和原型封装到一起; function Box(name,age){ // 将所有信息封装到构造函数体内; this.name = name; this.age = age; // 当第一次调用构造函数时,run()方法不存在,然后执行初始化原型; // 当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会载初始化; // 这样既得到了封装,又实现了原型方法共享,并且属性都保持独立; if(typeof this.run != 'function'){ // 仅在第一次调用时初始化; Box.prototype.run = function (){ return this.name+this.age+'运行中...'; }; } }; var box = new Box('lee',10); console.log(box.run()); // PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系;

9.寄生构造函数

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wgjxyp.html