美文网首页
三、理解原型对象

三、理解原型对象

作者: 萘小蒽 | 来源:发表于2019-03-22 01:16 被阅读0次

只要创建一个新的函数,都会根据特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象,默认情况下,原型对象都会自动获得一个constructor(构造函数),这个属性是指向prototype所在函数的指针。

function Person(){
   Person.prototype.name = "余嘉";
   Person.prototype.age = "27";
   Person.prototype.job = "Software Engineer";
   Person.prototype.sayName = function(){
        console.log(this.name);    
    };
}
var person = new Person();
var person1 = new Person();
console.log(Person.prototype);
//{name: "余嘉", age: "27", job: "Software Engineer", ....}
console.log(person1.__proto__);
//{name: "余嘉", age: "27", job: "Software Engineer", , ....}
console.log(person.__proto__.constructor == person.constructor) //true
console.log(person.constructo == person1.constructo)  //true
  • 如上代码,创建了自定义的构造函数后,其原型对象默认只会取得constructor属性,至于其他的方法,都是从Object继承而来的。
  • 当调用构造函数创建一个新的实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。
  • 我们称原型的指针叫「prototype」,但是实例中无法直接访问它,但是在每个对象上都支持_proto_这个属性去访问它。
构造函数-实例-原型关系
  • 根据上图,我们理清楚关系

  1. prototype指向的是构造函数的原型,可以用Person.prototype直接访问到它的原型.
  2. 原型的constructor指向prototype所在的构造函数这个方法Person.
  3. 而通过构造函数Person创建的实例person1,可通过_proto_访问到构造函数Person的原型。
  4. 创建的实例person1是没有包含属性和方法的,但我们可以调用person1.sayName(), 这是通过查找对象属性的过程来实现的(下面补充)

Person(){
    Person.prototype.name = "余嘉",
    Person.prototype.age = "27";
    Person.prototype.job = "Software Engineer";
}
var person1 = new Person();
var person2 = new Person();
   person1.name = "李刚"
console.log(person1.name) // "李刚"   来自实例
console.log(person2.name) // "余嘉"   来自原型
//判断是否为实例属性
person1.hasOwnProperty('name')  //true  来自实例
person2.hasOwnProperty('name')  //false  来自原型
//判断是否为原型属性(in操作符)
"name" in person1   //true  来自实例
"name" in person2   //true  来自原型
//删除实例属性
delete person1.name;
console.log(person1.name) // "余嘉"   来自原型

查找对象属性过程中---当我们访问person1.name的时候,需要读取它的值,因此就会在这个实例上搜索一个名为name的属性,当在实例上存在时,就会停止搜索原型,实例优先。

  • 实例上的属性与原型同名时,实例属性会屏蔽原型对象保存的同名属性。
  • 在通过 delete 操作符删除实例上的同名属性后,屏蔽解除。
  • 通过hasOwnProperty()方法可以判断属性是否来自于实例。
  • in操作符只要能在对象上查找到该属性(无论是实例还是原型),就会返回true
  • 在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated标记为false的属性).
  • 更简单的原型语法

function Person(){
};
Person.prototype = {
   name:"余嘉",
   age:27,
   job: "Software Engineer",
   sayName:function(){
    console.log(this.name)
    }
};
var person1 = new Person();
person1 instanceof Person; //true
person1 instanceof Object; //true
  • 上面将Person.prototype以对象字面量形式创建新的对象,最终的结果相同。

接上面的代码

var person1 = new Person();
person1 instanceof Person; //true

person1.constructor == Person  //false;
console.log(person1.constructor)  // ƒ Object() { [native code] }
  • 注意constructor不再指向Person了,因为prototype在默认生成的情况下,constructor才会指向构造函数本身,但是上面是直接Person.prototype = {}完全重写了默认的原型对象,因此constructor指向了Object,因为Person的原型,是Object的实例(以对象字面量形式创建的新的对象)。

下面这种,直接设置constructor指向,导致constructor变成了可枚举。

function Person(){
};
Person.prototype = {
   constructor:Person,
   name:"余嘉",
   age:27,
   job: "Software Engineer",
   sayName:function(){
    console.log(this.name)
    }
};
var person1 = new Person();
var arr = []
for(var i in person1){
  arr.push(i)
}
console.log(arr)
['constructor','name','age','job','sayName']

我们可以使用Object.definedProperty()去设置constructor属性,来解决上面的枚举问题。

  • 原型的动态性

我们对原型对象上做任何修改,都能从实例上反映(先创建实例,后修改也是)

//Person吐了
function Dog(){
  this.prototype.sayHi = function(){
     console.log('Hi')
  }
}
var dog = new Dog();
Dog.prototype.sayHi = function(){ 
          console.log("wang wang wang")
   }
dog.sayHi();   // wang wang wang

但是上面的情况里,重新设置构造函数的prototype,情况就不一样了。

function Dog(){
 }
var dog = new Dog(); //这里实例已经指向Dog开始原型了
Dog.prototype={ 
     sayHi:function(){ 
          console.log("wang wang wang")
     }
}
dog.sayHi();   // error
  • 这里说明实例和构造函数是没有直接关系的,上面其实是修改了Dog方法的原型指向,但是实例的原型指针还是修改之前的原型对象。
  • 原生对象的原型

讲了那么多自定义类型的原型模式的方面,原生的引用类型,都是采用这种模式创建的。原生引用类型(Object,Array,String,等等)都在其构造函数的原型上定义了方法。

typeof  Array.prototype.sort   //function
typeof  String.prototype.substring   //function

通过原生对象的原型,可以取得所有默认方法的引用,而且可以定义新的方法,和自定义对象的原型一样。

String.prototype.startsWith = function(text){
   return this.indexOf(text) == 0
}
var msg = "hello world!";
msg.startsWith("hello")  // true

不推荐在原生对象的原型上自定义方法,可能会导致命名冲突,也可能会意外地重写原生方法。

  • 原型对象的缺点

  1. 它省略了为构造函数传递初始化参数环节,导致所有实例在默认情况下都将取得相同的属性值。
  2. 原型的共享本质是最大问题,看下面例子
function Person(){
}
Person.prototype = {
    name:"余嘉",
    age: 27,
    firends:['Dog','Cat'],
}
var person1 = new Person();
var person2 = new Person();
person1.firends.push("Mouse");
console.log(person2.firends)
// ['Dog','Cat',"Mouse"]

这种共享带来的问题, 才导致很少有人单独使用原型模式,推荐下面的组合模式。

  • 组合使用\color{#c7254e}{构造函数模式}\\color{#c7254e}{原型模式}\

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。两种结合使用,每个实例都会有自己的一份实力属性的副本,但同时又共享着对方发的引用,最大限度节省内存,集两种模式的长处。

function Person (name,age,job){
     this.name = name;
     this.age = age;
     this.job = job;
     this.friends = ['Dog','Cat'];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
       console.log(this.name) ; 
  }
}
var person1= new Person("余嘉",27,"Software Engineer");
var person2= new Person("狗子",2,"看家护院");

上面生成的两个实例,把所有信息属性都封装在了构造函数中,而通过初始化构造函数的原型,既保持了自己某些方面的独立性,又公用着同一个方法。

相关文章

  • 三、理解原型对象

    只要创建一个新的函数,都会根据特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象,默认情...

  • 原型、原型链

    理解JavaScript原型 彻底理解JavaScript原型 原型 原型是一个对象,所有对象都可以成为原型,其...

  • javascript中面向对象编程-创建对象之原型模式

    理解名词:对象 原型对象 原型属性 函数 构造函数 实例 对象: Object,创建对象,对象属性方法原型对象:...

  • 廖雪峰JS小记

    (function(){})() 原型,原型链 浅谈Js原型的理解JS 原型与原型链终极详解 对象 对象:一种无序...

  • JS原型链简介

    要想理解原型链,我们要知道三个属性 1.__proto__ 所有对象都具有的属性,指向对象的原型对象 2.prot...

  • 理解原型对象

    1、构造函数function Person(){} 2、创建实例const person = new Person...

  • 我如何搞懂Javascript系列之原型和原型链

    理解原型 JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象,对象以其原型为模板、从原...

  • JavaScript---原型

    学习目标 使用 prototype 原型对象解决构造函数的问题 理解什么是原型(原型对象) 构造函数、protot...

  • php设计模式——原型模式

    原型模式 概念理解:原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象。适用于大对象的创建,...

  • 如何理解原型与原型链

    题目如何理解原型与原型链? 参考答案 构造函数有个prototype对象(原型),该对象有个“constructo...

网友评论

      本文标题:三、理解原型对象

      本文链接:https://www.haomeiwen.com/subject/jwftvqtx.html