js原型与继承

1.原型

class Person{
	constructor(name,age){
		this.name = name
		this.age = age
	}
	run = function(){
		console.log("他会跑");
	}
	eat(){
		console.log("他会吃");	
	}
}
let p = new Person("李华",20)
console.log(p);//Person {name: '李华', age: 20, run: ƒ}
console.log(p.eat);//ƒ eat(){console.log("他会吃");}

从代码和控制台打印的Person实例中可以看到,eat方法没有显示在Person实例里,但是能够访问到eat方法。那eat方法究竟去哪里了,我们看对象的内存空间。

每个对象都有一个隐含的__proto__属性,用来存储其他对象,而被存储的这个对象就是原型对象。当然,原型对象也是一个对象,它就也有__proto__属性, 所有的原型对象最终都指向Object原型,Object原型的__proto__为null。

class Person{
	constructor(name,age){
		this.name = name
		this.age = age
	}
	run = function(){
		console.log("他会跑");
	}
	eat(){
		console.log("他会吃");	
	}
}
let p = new Person("李华",20)
console.log(p.__proto__);//{constructor: ƒ, eat: ƒ}
console.log(p.__proto__.__proto__);
/* constructor: ƒ, __defineGetter__: ƒ,
__defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} */
console.log(p.__proto__.__proto__.__proto__);//null

从上面代码可以看到,eat方法在原型对象里。

对象中存储属性的区域实际有两个:
1)对象自身

  • 直接通过对象所添加的属性,位于对象自身中
  • 在类中通过 x = y 的形式添加的属性,位于对象自身中(如run = function(){ })

2)原型对象 (prototype)

  • 对象中还有一些内容,会存储到其他的对象里(原型对象)
  • 在对象中会有一个属性用来存储原型对象,这个属性叫做__proto__
  • 原型对象也负责为对象存储属性,当我们访问对象中的属性时,会优先访问对象自身的属性,对象自身不包含该属性时,才会去原型对象中寻找

会将对象属性添加到原型对象中的情况:
1)在类中通过xxx()方式添加的方法,位于原型中(如代码中的eat(){ }方法)

2)主动向原型中添加的属性或方法访问一个对象的原型对象

访问一个对象的原型对象

  • 对象.__proto__
  • Object.getPrototypeof(对象)

原型对象中的存在的数据

  • 对象中的数据(属性、方法等)
  • constructor (对象的构造函数)
class Person{
	constructor(name,age){
		this.name = name
		this.age = age
	}
	run = function(){
		console.log("他会跑");
	}
	eat(){
		console.log("他会吃");	
	}
}
let p = new Person("李华",20)
console.log(p.__proto__.constructor);
console.log(p.constructor);
/*底下是打印出来的constructor,两个打印的都是同一个实例对象,
这是因为原型对象上的属性可以直接通过对象拿到,如eat方法在原型对象中,p依旧可以拿到。
这是因为原型链的存在。
*/

/*
class Person{
	constructor(name,age){
		this.name = name
		this.age = age
	}
	run = function(){
		console.log("他会跑");
	}
	eat(){
		console.log("他会吃");	
	}
}
*/

注意:

  • 前面说到,原型对象也有原型,这样一层逃一层,就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同
  • p对象的原型链: p对象 --> Person原型--> Object原型 --> null
  • let obj =  { },obj对象的原型链: obj对象 --> Object原型 --> null

原型链:

读取对象属性时,会优先对象自身属性,如果对象中有,则使用,没有则去对象的原型中寻找,如果原型中有,则使用,没有则去原型的原型中寻找,直到找到Object对象的原型 (Object的原型没有原型 (为null )),如果依然没有找到,则返回undefined。


作用域链,是找变量的链,找不到会报错
原型链,是找属性的链,找不到会返回undefined

相同类的对象,它们的原型对象都是同一个,也就是说,相同类的对象的原型链是一样的。

如下面的person类的例子。

class Person{
	constructor(name,age){
		this.name = name
		this.age = age
	}
}
let p = new Person("李华",20)
let p2 = new Person("李红",18)
console.log(p.__proto__);
console.log(p2.__proto__);
console.log(p.__proto__ === p2.__proto__);

原型的作用

  • 原型就相当于是一个公共的区域,可以被所有该类实例访问,可以将一个该类实例中,所有的公共属性(方法) 统一存储到原型中,这样我们只需要创建一个属性,即可被所有实例访问。
  • 在对象中有些值是对象独有的,像属性(name,age,gender) 每个对象都应该有自己值,但是有些值对于每个对象来说都是一样的,像各种方法,对于一样的值没必要重复的创建,就可以在原型中创建。

修改原型

大部分情况下,我们是不需要修改原型对象

注意:

千万不要通过类的实例去修改原型

  • 通过修改一个实例对象影响所有同类对象,这么做不合适
  • 修改原型先得创建实例,麻烦
  • 危险

除了通过__proto _能访问对象的原型外,还可以通过类(构造函数)的prototype属性,来访问实例的原型,修改原型时,最好通过类去修改。

好处:

  • 一修改就是修改所有实例的原型
  • 无需创建实例即可完成对类的修改

原则:

  • 原型尽量不要手动改
  • 不要通过实例对象去改
  • 通过 类.prototype.属性 去修改
  • 不要直接给prototype去赋值(如Object.prototype = { })

总结:

  • 原型一般用来创建公共的属性或者方法,比如vue需要每个组件都能用的方法,那么直接把该方法添加到其原型上,Vue.prototype.add = function(){ }。
  • 当对象访问属性时,先在自身找,没找到就到自己的原型去找,没找到就去原型的原型找,直到Object原型(null),还没找到就返回undefined,如果中途找到了,就直接返回。
  • 除了Object.create(null)创建的对象没有原型,其他如字面量创建,new操作符创建的对象都会有一个__proto__属性依次指向原型对象,直到顶端Object原型null。
  • constructor:除了Object.create(null)创建的对象没有constructor,其他对象都有constructor属性,它返回一个构造它的构造函数,如下。
// 创建对象的形式有三种,字面量,new,Object.create()
// 字面量形式创建是new创建的简洁版,所以对象o的constructor是它的构造函数Object
const o = {}
o.constructor === Object // true
const o1 = new Object
o1.constructor === Object // true

const a = []
a.constructor === Array // true
const a1 = new Array
a1.constructor === Array // true
  • prototype:它是类(构造函数)的属性,通过它也可以访问到原型对象,也就是说,实例对象的__proto__属性全等于类(构造函数)的prototype属性。
let obj = {}
console.log(obj.__proto__ === Object.prototype);//true
class Per{}
let a = new Per()
console.log(a.__proto__ === Per.prototype);//true

*原型在控制台表示为  [[Prototype]]: 原型对象

ES5的类(构造函数)与ES6的类的区别

// ES5的类,构造函数
function Animal(name,age){
	this.name = name,
	this.age = age
}
Animal.prototype.eat = function(){ //原型上的方法
	console.log("吃东西");
}
// 静态属性和方法只能由类本身调用
Animal.weight = 800 //静态属性
Animal.say = function(){ // 静态方法
	console.log("你好");
}
let a = new Animal("狗",4)
console.log(a);//Animal {name: '狗', age: 4}
Animal.say()//你好

// ES6的类
class Animals{
	constructor(name,age){
		this.name = name,
		this.age = age
	}
	static weight = 800 //静态属性
	eat(){//原型上的方法
		console.log("吃东西");
	}
	static say(){ // 静态方法
		console.log("你好");
	}
}
let b = new Animals("猫",2)
console.log(b);//Animals {name: '猫', age: 2}
Animals.say()//你好

一句话概括,类就是构造函数的语法糖,虽然写法不同,原理是一抹一样。

2.继承

继承的本质就是把父类设置为子类的原型,子类就可以随便用父类的属性和方法,如果不想用,在子类重新设置属性或方法就可以覆盖原型的(父的)属性或方法。

// ES5实现继承
function Animal(){
	this.name = "狗剩"
}
function Dog(){
	
}
Dog.prototype = new Animal()	
let p = new Dog()
console.log(p);//[[Prototype]]:Animal
console.log(p.name);//狗剩
// ES6实现继承
class Person{
	constructor(name,age){
		this.name = name,
		this.age = age
	}
}
class Per extends Person{
// 这里不设置属性,相当于把父类代码复制下来,其实就是把父类设为子类原型
}
let p2 = new Per("张三",12)
console.log(p2);//Per {name: '张三', age: 12} [[Prototype]]: Person
class Person{
	constructor(){
		this.name = "二狗"
		this.age = 13
	}
}
class Per extends Person{
// 如果这里有constructor,整个父类的constructor里的属性就访问不到了
// 因为先访问对象,如果找不到属性再去原型,constructor出现意味着不会去原型找
// 要想再次拿到父类的属性,传入super()参数即可
	constructor(age){
		super(name)//相当调用父类构造函数,传入的参数就是父类的参数
		this.age = age//对父类的某属性重新赋值,或新增属性
	}
}
let p2 = new Per(4)
console.log(p2);//Per {name: '二狗', age: 4}

3.new操作符在创建对象的时候干了什么? 

  •  创建一个普通的JS对象 (Object对象 )
  • 将构造函数的prototype属性设置为新对象的原型
  • 使用实参来执行构造函数,并且将新对象设置为函数中的this
  • 如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值返回,如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值返回。通常不会为构造函数指定返回值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值