JS中的原型与原型链

原型对象

一、什么是原型

原型是Javascript中的继承的基础,JavaScript的继承就是基于原型的继承。

二、函数的原型对象

创建一个函数A(就是就是声明一个函数),那么浏览器就会在内存中创建一个对象B,而且每个函数都默认会有一个属性 prototype 指向了这个对象,( 即:prototype的属性的值是这个对象 )。这个对象B就是函数A的原型对象,简称函数的原型。这个原型对象B 默认会有一个属性 constructor 指向了这个函数A ( 意思就是说:constructor属性的值是函数A )。

function Person(){
	
}
//这里我们声明了一个函数Person,那么浏览器就在内存中创建了一个对应的原型对象,
//这个函数中有属性prototype属性,这个属性指向了这个原型对象(也就是prototype的属性值
//就是这个对象在内存中的地址);这个原型对象中有一个constructor的属性,这个属性指向
//了函数A(也就是constructor的属性值是函数在内存中的地址)

//注意:原型对象默认只有属性:constructor。其他都是从Object继承而来,暂且不用考虑

在这里插入图片描述
注意:原型对象默认只有属性:constructor。其他都是从Object继承而来,暂且不用考虑。

三、使用构造函数创建对象

当把一个函数作为构造函数 (理论上任何函数都可以作为构造函数) 使用new创建对象的时候,那么这个对象就会存在一个默认的不可见的属性,来指向了构造函数的原型对象。

function Person(){
}
var person = new Person();
//我们用构造函数创建了一个实例的时候,这个对象会自动添加一个
//不可见的属性 [[prototype]], 而且这个属性,指向了构造函数的原型对象。

在这里插入图片描述
注:

  1. 使用new Person创建的所有实例对象,其__proto__属性都指向构造函数的原型对象。
  2. 在原型对象中手动添加属性和方法,那么所有__proto__属性指向该原型对象的实例对象,都会共享这些属性和方法。
  3. 如果访问person的属性name,如果在person对象中找到了,那么就返回person的name;如果在person中没有找到,则会去person的__proto__属性指向的原型对象中找,找到了则返回原型对象中的name;如果还没找到,则会去原型对象的原型对象对应的原型对象中找(涉及原型链)。
  4. 通过person对象只能读取原型中的属性name的值,而不能修改原型中的属性name的值。 person.name = “李四”; 并不是修改了原型中的值,而是在person对象中给添加了一个属性name。
var p1 = new Person();
var p2 = new Person();
Person.prototype.name = "原始人";
Person.prototype.age = 100;
Person.prototype.shout = func;

//p1与p2会继承原型中的属性和方法
console.log(p1.name);
console.log(p1.age);
p1.shout();

//把p2中继承的属性覆盖
p2.name = 'lisi';
p2.shout = function (){
	console.log("哈哈哈");
}
console.log(p2.name);
p2.shout();

四、与原型有关的属性和方法

1. proto 属性(注意:左右各是2个下划线)

用构造方法创建一个新的对象之后,这个对象中默认会有一个不可访问的属性 [[prototype]] , 这个属性就指向了构造方法的原型对象。

​ 但是在个别浏览器中,也提供了对这个属性[[prototype]]的访问(chrome浏览器和火狐浏览器。ie浏览器不支持)

function Person(){

}

//用Person.prototype修改构造函数的prototype指向
Person.prototype = {
	name:'lisi',
	age:12
}
var p = new Person();
console.log(Person.prototype === p.__proto__);	//true

//用p.__proto__修改实例对象p的__proto__指向
p.__proto__ = {
	name:'wangwu',
	age:30
}
console.log(Person.prototype === p.__proto__);	//false

//创建p2实例对象,看看两者对应的原型对象是否相同
var p2 = new Person();
console.log(p2.__proto__ === p.__proto__);	//false

//看看p2对应的原型对象是否与Person的原型对象相同
console.log(p2.__proto__ === Person.prototype);	//true
  1. 用Person.prototype修改构造函数指向的原型对象后,其所有的实例对象所对应的原型也会改变
  2. 用p1__proto__改变这个实例对象对应的原型指向后,Person.prototype不改变,只改变这一个实例对象的__proto__指向,其他的实例对象__proto__依旧与Person.prototype保持一致。

2. hasOwnProperty()

function Person(){

}

Person.prototype.name = "lisi";
Person.prototype.shout = function (){
	console.log("aowu");
}
var p = new Person();
p.gender = "woman";
p.move = function (){
	console.log("fast");
}

console.log(p.hasOwnProperty("gender"));	//true
console.log(p.hasOwnProperty("move"));		//true

console.log(p.hasOwnProperty("name"));		//false
console.log(p.hasOwnProperty("shout"));		//false

hasOwnProperty用来判断属性和方法是否在本对象中添加,而不会算原型对象继承来的属性和方法。

3. in操作符

function Person(){
	location: "bb"
}

Person.prototype.name = "lisi";
Person.prototype.shout = function (){
	console.log("aowu");
}

var p = new Person();
p.gender = "woman";
p.move = function (){
	console.log("fast");
}

console.log("name" in p);
console.log("gender" in p);
  1. in操作符用来判断一个属性是否存在于这个对象中。但是在查找这个属性时候,现在对象本身中找,如果对象找不到再去原型中找。换句话说,只要对象和原型中有一个地方存在这个属性,就返回true

  2. 如果一个属性存在,但是没有在对象本身中,则一定存在于原型中。

五、关于原型对象的总结

  1. 每一个构造函数都有一个原型对象,在构造函数中有有一个prototype的属性,这个属性指向了相关联原型对象。
  2. 原型对象中有一个constructor的属性,这个属性指向了其构造函数。由于构造函数创建的实例__proto__默认指向构造函数的原型对象,实例会继承原型对象的所有属性,则constructor属性也会被继承。
  3. 每个对象都有一个__proto__的属性,这个属性指向了它们构造函数(constructor)的原型对象。这个属性指向哪个对象,哪个对象的所有属性就都会被继承。我们一般不修改__proto__属性的指向。
  4. 原型对象也是对象,也有__proto__属性,它的__proto__默认指向的是Object()的原型对象
  5. 构造函数也是对象,所有的构造函数的__proto__指向的都是Function()的原型对象

六、组合原型模型和构造函数模型创建对

1.原型模型创建对象的缺陷

原型中的所有的属性都是共享的。也就是说,用同一个构造函数创建的对象去访问原型中的属性的时候,大家都是访问的同一个对象,如果一个对象对原型的属性进行了修改,则会反映到所有的对象上面。

​ 但是在实际使用中,每个对象的属性一般是不同的。张三的姓名是张三,李四的姓名是李四。

​ **但是,这个共享特性对 方法(属性值是函数的属性)又是非常合适的。**所有的对象共享方法是最佳状态。这种特性在c#和Java中是天生存在的。

2.构造函数模型创建对象的缺陷

​ 在构造函数中添加的属性和方法,每个对象都有自己独有的一份,大家不会共享。这个特性对属性比较合适,但是对方法又不太合适。因为对所有对象来说,他们的方法应该是一份就够了,没有必要每人一份,造成内存的浪费和性能的低下。

function Person() {
	    this.name = "李四";
	    this.age = 20;
	    this.eat = function() {
	        alert("吃完东西");
	    }
	}
var p1 = new Person();
var p2 = new Person();
//每个对象都会有不同的方法,虽然他们的表现一样,但在内存中占了两个位置
console.log(p1.eat === p2.eat); //fasle

3.使用组合模式解决上述两种缺陷

原型模式适合封装方法,构造函数模式适合封装属性,综合两种模式的优点就有了组合模式。

function Person(name,age){
	//每个对象都添加自己的属性
	this.name = name,
	this.age = age;
	/*
		判断this.shout这个属性是不是function,如果不是function,则证明是第一次创建	
		对象,则将这个function添加到原型对象中;
		如果是function,则代表原型中已经有了这个方法,则不需要再添加。
	*/
	if(typeof this.shout != "function"){
		Person.prototype.shout = function(){
			console.log("ahah");
		}
	}
}
var p = new Person("lisi",20);
p.shout();

原型链

一、继承的概念

对JavaScript来说,没有类和接口的概念(ES6之前),所以只支持实现继承,而且继承在 原型链 的基础上实现的。等了解过原型链的概念之后,你会发现继承其实是发生在对象与对象之间。这是与其他编程语言很大的不同。

二、原型链的概念

在JavaScript中,将原型链实现继承的主要方法。原型链其实简单来说就是用原型形成的链条。因为原型对象中的__proto__属性指向其构造函数的原型,在访问对象的属性的时候,会先在本对象中找,如果没找到,在去其__proto__指向的原型对象中找,如果还没找到,就会到原型对象中的__proto__属性指向的原型对象中去找,直到原型链顶端null;

再回顾一下原型对象,构造函数,对象之间的关系。每个构造函数都有一个prototype属性指向相关联的原型对象,每个原型对象中都有一个constructor属性指向相关联的构造函数。每个对象中都有一个__proto__属性来指向其构造函数的原型对象。每当访问对象的属性和方法的时候,都会先在本对象中寻找,找不到再去指向的原型对象中找。

function Person() {
	this.name = "Mr.Q";
}
Person.prototype.play = function(){
	console.log("Person中的play方法");
}

function Man(){
	this.age = 22;
}

//把Man构造方法相关联的原型对象替换成Person的实例对象
Man.prototype = new Person();

Man.prototype.work = function(){
	console.log("Man中的work方法");
}

var m = new Man();
m.play();
m.work();	
console.log(m.name);
console.log(m.age);

说明:

  1. 首先,我们定义了一个Man函数后,浏览器创建了一个相关联的原型对象。但是,我们通过Man.prototype = new Person后,将Man构造函数的原型对象替换成了Person的实例对象。而Person的实例对象的__proto__属性指向Person函数的原型对象。
  2. 在访问属性和方法的时候,查找顺序是在原型链中查找的。查找顺序如下:对象–>原型对象–>原型对象的原型对象–>…–>原型链的顶端。就像一个链条一样,这样由原型连成的"链条",就是我们经常所说的原型链

三、默认顶端原型

在 JavaScript 中所有的类型如果没有指明继承某个类型,则默认是继承的 Object 类型。这种 默认继承也是通过原型链的方式完成的。
在这里插入图片描述
在这里插入图片描述

  1. 原型链的顶端一定是Object的原型对象。这也是为什么我们随意创建一个对象,就有很多方法可以调用,其实这些方法都是来自Object的原型对象。
  2. 通过对象访问属性方法的时候,一定是会通过原型链来查找的,直到原型链的顶端。

四、测试数据类型

  1. **typeof:**一般用来测试简单数据类型和函数的类型。如果用来测试对象,则会一直返回object,没有太大意义。
console.log(typeof 1);	//number	
console.log(typeof function(){});	//function
function Person() {

}
console.log(typeof new Person);		//object
  1. instanceof: 用来测试一个对象是不是属于某个类型。结果为boolean值。
function Person(){

}
function Man(){

}
Man.prototype = new Person();
var man = new Man();
console.log(man instanceof Man);	
console.log(man instanceof Person);//Man通过原型继承了Person
console.log(man instanceof Object);//Man通过原型继承了Object
  1. .isPropertyOf(对象):这个方法是原型对象中的方法,用来判断这个原型对象是不是参数对象原型链中的一环
function Person(){

}
function Man(){

}
Man.prototype = new Person();
var man = new Man();
console.log(Man.prototype.isPrototypeOf(man));
console.log(Person.prototype.isPrototypeOf(man));
console.log(Object.prototype.isPrototypeOf(man));

五、原型继承中的缺陷

function Person(){
	this.girls = ["wangbao","jingbao"];
}
function Man(){

}

Man.prototype = new Person();	//新建的Person实例中有girls数组,而这个实例又是Son的原型对象
var man1 = new Man();	//从原型中继承了girls
var man2 = new Man();	//从原型中继承了girls
						//这两个girls实际上是同一个girls,都是原型对象中的girls数组

man1.girls.push("kuku");
console.log(man2.girls);	//[ 'wangbao', 'jingbao', 'kuku' ]

在原型继承的方式中,我们把父类的实例作为子类的原型,此时子类的实例共享了父类构造函数的引用属性

六、调用继承(没有真的实现继承)

function Person(name, age){
	this.name = name;
	this.age = age;
	// console.log(this);	//globle
}

function Man(name, age, sex){
	this.sex = sex;
	//Person函数中的this指向当前的this(Man类型的实例对象)
	Person.call(this,name, age);

	//Person函数中的this指向的是全局变量global,因为相当于是直接调用了这个函数,
	//而这个函数被挂载在了global对象上
	//Person(name,age);	
}

var man = new Man("lisi", 30, '男');
//new关键字的作用
//1.首先在内存中创建了一个空对象,并让这个空对象__proto__指向构造函数的原型对象
//2.将空对象作为构造函数this的上下文,执行构造函数
//3.返回对象地址
// console.log(global.name);
console.log("name: " + man.name + " " + "age: " + man.age +" " + "sex:" + man.sex);
console.log(man instanceof Person);

七、组合继承

组合继承利用了原型继承和构造函数继承的优点

//父类型构造函数
function Person(name, age){
	this.name = name;
	this.age = age;
	if((typeof Person.prototype.play) != 'function'){
		Person.prototype.play = function(){
			console.log(this.name + "is playing");
		}
	}
}

//子类型构造函数
function Man(name, age, gender){
	//调用父类型的参数,但是父构造函数中的this却是子类中的this
	Person.call(this, name, age);
	this.gender = gender;
}

//修改子类型的原型
Man.prototype = new Person();
var man1 = new Man("libao", 20, '男');
console.log(man1.name, man1.age, man1.gender);

注:Man.prototype = new Person(),这一步让子类的原型对象继承了父类的方法和属性,但是由于var man1 = new Man(“libao”, 20, ‘男’);由子类构造函数构造出来的对象中的属性覆盖掉了从原型对象中继承来的属性。

本篇博客只是在别人博客的基础上加上了自己的理解和一些测试,原文链接如下
继承中的原型链
JavaScript中原型对象的彻底理解
原型与原型链

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值