js对象原型,原型链

1.理解原型

无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象),默认情况下,所有原型对象自动获得一个名为construcor的属性,指回与之关联的构造函数,

function Person(){ }

Person.prototype.name='Nicholas'; 

Person.prototype.age=29;

Person.prototype.sayName=function(){

   console.log(this.name)

}; 

let person1=new Person(); 

Person1.sayName(); //Nicholas


let person2=new Person();

person2.sayName( ); //"nicholas"


console.log(person1.sayName == person2.sayName); //true 

对前面的例子而言,Person.prototype.constructor指向Person 然后,因构造函数而异,可能会给原型对象添加其他属性和方法 

在自定义构造函数时,原型对象默认只会获得construcotr属性,其他的所有方法都继承自Object.每次调用构造函数创建一个新实例,这个实例的内部[[construcotr]]指针就会被赋值为构造函数的原型对象,脚本中没有访问这个[[Prototype]]特性的标准方式,但Firefox.Safari和Chrome会在每个对象上暴露__proto__属性,通过这个属性可以访问对象的原型,在其他实现中,这个特性完全被隐藏了,关键在于理解这一点: 实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有

可以通过下面的代码来理解原型的行为:  

//构造函数

function Person(){ }

//声明之后,构造函数就有了一个与之关联的原型对象: 
console.log(typeof Person.prototype); 

console.log(Person.prototype)   //谷歌  {

                                                                 construcotr: f  Person(), 

                                                                  __proto__: Object

                                                              }

如前所述,构造函数有一个prototype属性 引用其原型对象,而这个原型对象也有一个 construcotr属性,引用这个构造函数 换句话说,两者循环引用:

console.log(Person.prototype.construcotr === Person); //true

正常的原型链都会终止与Object的原型对象 Object 原型的原型是null

//原型对象的__proto__== 构造函数的原型对象  因为Person.prototype本身是由Object构造函数创建的对象,所以Person.prototype.__proto__是指向Object.prototype

console.log(Person.prototype._proto__ === Object.prototype); //true

//构造函数的原型对象的construcotr===构造函数 console.log(Person.prototype._proto_.constructor===Object);  //true

//Object.prototype._proto_  ==null

conosle.log(Person.prototype._proto_._proto_===null)

console.log(Person.prototype._proto_); 

let person1 = new Person( ), 

     person2 = new Person( );  

 构造函数,原型对象和实例时3个完全不同的对象:

console.log(person1 !== Person); //true

console.log(person1 !== Person.prototype); //true

console.log(Person.prototype !== Person); //true

实例通过_proto_链接到原型对象,它实际上指向隐藏特性[[Prototype]]构造函数通过prototype属性链接到原型对象,实例与构造函数没有直接联系,与原型对象有直接联系 

console.log(person1._proto_ === Person.prototype); //true 

console.log(person1._proto_.construcotor)===Person //true 

同一个构造函数创建的两个实例,共享同一个原型对象: 

console.log(person1._Proto_) === person2._proto_; //true 

instanceof检查实例的原型链中

//是否包含指定构造函数的原型

console.log(person1 instanceof Person); //true 

console.log(person1 instanceof Object); //true 

console.log(Person.prototoype instanceof Object); //true 

注意: 

  • Person.prototype指向原型对象,而Person.prototype.contructor指回Person构造函数
  • 两个实例person1和person都只有一个内部属性指回Person.prototype,而且两者与构造函数没有直接联系 
  • 虽然这个两个实力都没有属性和方法,但person1.sayName()可以正常调用,这是由于对象属性查找机制的原因 
isPrototoype(object): 判断某个对象是否是另一个对象原型链中的一个节点。

object:  你要检查的目标对象

console.log(Person.prototype.isPrototypeOf(person1)); //true 

console.log(Person.prototype.isPrototypeOf(person2)); //true 

//因为这两个例子内部都有链接指向Person.prototype 

 Object.getPrototypeOf( ), 返回参数的内部特性[[Prototype]]的值, 

console.log(Object.getPrototypeOf(person1)=== Person.prototype); //true 

console.log(Object.getPrototypeOf(person1).name); //Nicholas

2.原型层级 

在通过对象访问属性时,会按照这个属性的名称开始搜索,搜索开始于对象实例本身,如果在这个实力上发现了给定的名称,则返回该名称对应的值,如果没有找到这个属性,则搜索会沿着指针进入原型对象,然后在原型对象上找到属性后,再返回对应的值, 

在调用person1.sayName()时,会发生两步

  • 首先,js引擎会问person1 实例有sayName属性吗? 没有
  • 然后, person1的原型有sayName属性吗? 有    

在调用person2,sayName()时,会发生同样的搜索过程,而且也会返回相同的结果,这就是原型用于在多个对象实力间共享属性和方法的原理 

虽然可以通过实例读取原型对象上的值,但不可能通过实例重写这些值,如果在实例上添加了一个与原型对象中同名的属性,那就会在实例上创建这个属性,这个属性会遮住原型对象上的属性 

function Person(){ }

Person.prototype.name='Nicholas'; 

Person.prototype.age=29;

Person.prototype.sayName=function(){

   console.log(this.name)

}; 

let person1=new Person();

let person2=new Person();


console.log(person1.hasOwnProperty('name')); //false 

console.log('name' in person1) //true


person1.name="Greg"; 

console.log(person1.name); //Greg 来自实例

//in操作符

console.log('name' in person1) //true

console.log(person2.hasOwnProperty('name')); //true 

console.log(person2.name); //Nicholas  来自原型  
 

hasOwnProperty():用于确定某个属性实在实例还是在原型对象上,这个方法是继承自Object的,会在属性存在于调用它的对象实例上时返回true

注意: js的Object.getOwnPropertyDescriptor( )方法只对实例属性有效,要取得原型属性的描述符,就必须直接在原型对象上调用Object.getOwnProperty-Descriptor().

3.原型和in操作符 

有两种方式使用in操作符: 单独使用和在for-in循环使用时,in操作符会在可以通过对象访问指定属性时返回true,无论该属性是在实例上还是在原型上,以上面为例

在for-in 循环中使用 in 操作符时,可以通过对象访问且可以被枚举的属性都会返回,包括实例属性和原型属性, 遮蔽原型中不可枚举([[Enumerable]]特性被设置为false)属性的实例属性也会在for-in循环中返回,因为默认情况下开发者定义的属性都是可枚举的 

Object.keys()方法, 接收一个对象作为参数,返回包含该对象所有可枚举属性名称的字符串数组

console.log(Object.keys(Person.prototype)) // [name,age,sayName]

let p1=new Person();

p1.name="Rob";

p1.age=31

console.log(Object.keys(p1)) //[name,age]

在Person的实力上调用时,Object.keys()返回的数组中只包含"name"和"age";两个属性

Object.getOwnPropertyNames(); 无论是否可以枚举,列出所有实例属性

let keys=Object.getOwnPropertyNames(Person.prototype);

console.log(keys); '[constructor,age,name,sayname]'

注意: 返回的结果中包含了一个不可枚举的属性construcotor,Object.keys()和object. getOwnPropertyNames()在适当的时候都可用来代替for-in循环 

在es6新增符号类型后,相应地出现了增加了恶一个Object.getOwnPropertyNames()的兄弟方法的需求,因为以符号为键的属性没有名称的概念,因此,Object.getOwnProperty-Symbols()方法就出现了,这个方法与object.getOwnPropertyNames()类似,只是针对符号而已:

let k1=Symbol('k1'),

     k2= Symbol('k2'); 

let o={

   [k1]:'k1',

   [k2]:'k2'

}; 

console.log(Object.getOwnPropertySymbol(o)); 

//[Symbol(k1),Symbol(k2)]

4.其他原型语法(重写原型)

function Person(){ }

Person.prototype={
  name:'Nicholas',
  age:29,
  sayName(){
    console.log(this.name)
  }
}

注意: 这样重写之后,Person.prototype的construcotr属性就不指向Person了,在创建函数时,也会创建它的Prototype对象,同时会自动给这个原型的construcotr属性赋值,而上面的写法完全重写了默认的prototype对象,因此其constructor属性也指向了完全不同的新对象(Object 构造函数),不再指向原来的构造函数,

5.原型的动态性

因为从原型上搜索值的过程是动态的,所以即使实例在修改原型之前已经存在,任何时候对原型对象所做的修改也会在实例上反映出来

let firend = new Person( ); 

Person.prototype.sayHi=function(){

  conosle.log('HI')''

};

friend.sayHi(); //hi 

调用friend.sayHi发生: 在这个实例中搜索名为sayHi的属性,没有找到,继续搜索原型对象,因为实例和原型之间的连接就是简单的指针,而不是保存的副本,所以会在原型上找到sayHi并返回这个属性

注意: 

1.这个跟重写是两回事, 实例的[[Prototype]]的指针是在调用构造函数时自动复制的,这个指针即使把原型修改为不同的对象也不会变

 2. 重写整个原型会切断最初原型与构造函数的联系,但实例引用的是最初的原型,记住,实例只有指向原型的指针,没有指向构造函数的指针

function Person(){ }

let friend =new Person(); 

Person.prototype={
  constructor: Person,
  name:"Nicholas",
  age:29,
  job:'Software Engineer'
  sayName(){
    console.log('11111')
  }
}

friend.sayName(); //错误

在这个例子中,Person的新实例是在重写原型对象之前创建的,在调用friend.SayName()时候,会导致错误,这是因为firend指向的原型还是最初的原型,而这个原型上并没有sayName属性 

 

重写构造函数上的原型之后再创建的实例才会应用新的原型,而在此之前创建的实例仍然会引用最初的原型 

 6.原型对象原型

原型模式之所以重要,不仅提现在自定义类型上,而且还因为它也是实现所有原生引用类型的模式

所有原生引用类型的构造函数(包括Object,Array,String等)都在原型上定义了实例方法,比如,数组的实例的sort()方法就是Array.prototype上定义的,而字符串包装对象的substring()方法也是在String.prototype上定义的 

下面的代码给string原始值包装类型的实例添加了一个startsWith()方法:  

string.prototype.startsWith=function(text){

   return this.indexOf(text) ===0;

};

let msg='Hello world!';

console.log(msg.startsWith('Hello')); //true 

注意:不推荐这样做,可能会引发命名冲突, 引发误会, 推荐的做法是创建一个自定义的类继承原生类型 

7.原型链 

其实就是一种查找机制,用于实现对象之间的继承关系,当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,js引擎会沿着原型链向上查找,知道找到对应的属性或方法或者到达原型链的顶端(即Object.prototype). 通过原型链,js实现了基于原型的继承机制,这是的对象之间可以共享属性和方法,同时也保留了各自的特定属性和方法 

function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function () {
  console.log("Hello, I am " + this.name);
};

const p1 = new Person("Tom");

p1.sayHello(); // Hello, I am Tom

//原型链  

p1 --> Person.prototype --> Object.prototype --> null

8.原型的问题:

  1. 弱化了向构造函数传递初始参数的能力,会导致所有实例默认都取得相同的属性值

  2. 共享特性: 原型上所有属性都是在实例间共享的,这对函数来说比较合适,另外包含原始值的属性也还好,可以通过在实例上添加同名属性来简单的遮蔽原型上的属性,真正的问题来自包含引用值的属性

  3. function Person( ){ }

    Person.prototype={

        constructor: Person,

        friends: ['Shelby','court'] 

    }

    let person1=new Person();

    let person2=new Person(); 
    person1.friends.push('van');


    conosle.log(person1.friends); //Shelby,Court,Van 

    console.log(person1.friends === person2.friends); //true 

        由于这个friends属性存在于Person.prototype 而非person1上,新加的这个字符串也会在(指向同一个数组的)person2.firends上反映出来,如果这是有意在多个实例共享数组,那没什么问题,但一般来说,不同的实例应该有属于自己的属性副本,这就是实际开发中通常不单独使用原型模式的原因 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值