原型+原型链与继承
首先明确一点,一般说的对象,指的是已经实例化以后的;
功能作用:显示一个对象所有的属性和方法 可以看出原型链关系
使用普通构造函数生成的实例对象和构造函数console.dir
以后;
结果说明:
console.dir(Person)
的结果 构造函数内容 + prototype+__proto__
(函数自己的原型Function.prototype
)
console.dir(per)
的结果是 其构造函数定义的属性和方法(是赋值之后的)+__proto__
(里面有一个constructor
和__proto__
) ------------为什么是这样的结果,后面把原型链讲完就明了
总结:
> 实例对象dir
后 会显示 在构造函数中定义的属性和方法(是赋值之后的)
> 构造函数内容中 并没有这些属性和方法 (没有 age和name 等)
> 函数都有 prototype属性, 对象都有__proto__
属性
另外:如果按照实际结构:
本来是应该是 per.__proto__.constructor==Person;
但是规定 per.constructor==Person;
所以 console.log(per.constructor==Person) //true
(虽然per并没有这个constructor属性的,但是有规定)
per.__proto__.constructor==Person
和instanceof
都是可以检测 引用类型的方法
-
通过原型解释实例对象和 构造函数的结构
-
构造函数可以实例化对象
-
构造函数中有一个属性叫prototype, 是构造函数的原型对象(把这个看成一个整体)
**这部分就叫原型**
-
构造函数的原型对象(prototype)中,也就是原型,它有两个属性:
constructor
和__proto__
constructor
指向的就是 该原型对象所在的构造函数
__proto__
指向自己的原型对象 (这个时候这个原型 看成是另一个更高级原型的实例对象) (可以看出 原型和 构造函数 是相互指向的,也比实例对象 的级别高些)
-
实例对象的原型对象(proto)指向的是该构造函数的原型对象
实例对象的原型(
__proto__
)和 构造函数的原型 (prototype
) 内部结构一模一样即:
per.__proto__===Person.prototype
可以感受到 实例对象的级别低一级
-
通过构造函数的原型对象(prototype)中的方法是可以被实例对象直接访问的
通过构造函数原型 添加的方法 (就是通过原型添加的方法)和直接在构造函数添加的方法在
console.dir()
的时候 所处的位置是不一样的: 通过原型添加的方法 只能在 原型对象中能找到,直接在实例对象中是没有的-------确实也应该是这样
-
原型中的方法也是可以相互访问的:(在调用eat()中调用play) 实例对象中的方法,是可以相互调用的
-
实例对象使用的属性或者方法, 先在实例中查找, 找到了则直接使用,找不到则去实例对象的原型
__proto__
中去找。 -
原型的指向是可以改变的
实例对象的原型
__proto__
指向的是该对象所在的构造函数的原型对象, 当构造函数的原型对象(prototype
)指向如果改变了,实例对象的原型(__proto__
)指向也会发生改变
Student.prototype=new Person(10);
-------改变student的原型 这时候Student的原型 就是Person的实例对象;可以观察从继承看原型链图好好观察
在原有的原型链上增加了一环
指向改变后 ,通过Student实例化后的对象 也可以使用Person中的方法,(因为它是原型)
如果想添加新的方法 必须在改变指向后 添加新的方法到原型;因为 改变指向是
Student.prototype=new Person(10)
这种直接改变对象的指向添加的 所以 之前添加的会被覆盖。
实例对象、原型以及构造函数三者之间的关系:
-
-
原型的应用:
-
数据共享 —节约资源
-
将需要共享的数据放到原型中,不需要共享的数据放到 构造函数中
直接
Person.prototype.newMethod
添加, 因为通过原型添加的方法和属性,实例对象也是可以访问到的。
-
-
继承:后面介绍
-
-
原型链及原型链的最终指向:
一个原型对象 也是有原型的(这时候 可以看成原型对象是一个 实例对象),
举例:new Person() 是一个实例对象,其原型对象 是Person.prototype ,(person.__proto__ 指向 同Person.prototype)原型对象 也是一个对象 (可以认为它是 new Object 出来的实例对象 所以) 它的原型是 Object.prototype 所以结构就是: per----》Person.prototype----》Object.prototype----》null (下一级原型相对 上一级 就像是一个实例对象 )
继承
-
为什么要使用继承
继承是为了解决冗余代码(两个对象有很多共同的属性方法,但又有不同的,这时候只用构造函数就不可以做到了,但是每一个都写一个构造函数,又会造成冗余,这时候可以采取继承,减少代码编写)
面向对象编程思想:根据需求,分析对象,找到对象有什么特征和行为,通过代码的方式来实现需求,要想实现这个需求,就要创建对象,要想创建对象,就应该显示有构造函数,然后通过构造函数来创建对象. 通过对象调用属性和方法来实现相应的功能及需求,即可---------------抽象出一个构造函数出来就相当于是在面对对象编程了。
对于
JS
而言,它不是一门面向对象的语言, 而是一门基于对象的语言,那么为什么学习js还要学习面向对象,因为面向对象的思想适合于人的想法,编程起来会更加的方便, 及后期的维护面向对象的编程语言中有类(class)的概念(也是一种特殊的数据类型),但是
JS
不是面向对象的语言,所
以,原生JS
中没有类(class
),但是可以模拟面向对象的思想编程, 会通过构造函数来模拟类的概念(class) -
js
继承实现的几种方式因为继承是必须要得到 父级中的所有属性方法(所以apply和原型在继承中必不可少使用)
-
借用构造函数继承+apply
缺点:父类原型上的东西是没法继承的
function Person(name, age) { this.name = name; this.age = age; this.arr=[1,2,3,4]; this.go=function () { console.log("gogogog") } } Person.prototype.sayHi = function () { console.log("您好"); }; function Student(name,age,score) { //借用构造函数 Person.call(this,name,age); this.score = score; } var stu1 = new Student("小明",10,"100"); console.log(stu1.name, stu1.age, stu1.score); stu1.sayHi()------------------报错
-
借用原型链继承
function Person(name, age) { this.name = name; this.age = age; this.arr=[1,2,3,4]; this.go=function () { console.log("gogogog") } } Person.prototype.sayHi = function () { console.log("您好"); }; function Student(score) { this.score = score; } //通过改变原型获取父类的所有属性和方法 Student.prototype=new Person("xiaoxin",10)
这样确实可以获取所有父级的方法属性(包括原型中的)
但是也有缺点
第一点: 因为 改变指向的时候
Student.prototype=new Person("xiaoxin",10)
所以通过new Student出来的子类name值和 age值初始值是固定好的;
不能灵活的自己定义自己的初始属性值。
第二点:因为改变指向 用的 是
Student.prototype=new Person("xiaoxin",10)
这个 每一个 Student实例的原型 ,上级,也就是“根” 的存在;
当在利用new Student 出多个实例的时候,其中一个更改student 原型(new Person实例)中 的是引用类型的属性,每个实例都随之改变。
这块有点绕,以后慢慢理解
var stu1=new Student(100); console.log(stu1.name+"=="+stu1.age+"=="+stu1.arr+"=="+stu1.score) var stu2=new Student(80); console.log(stu2.name+"=="+stu2.age+"=="+stu1.arr+"=="+stu2.score) stu1.name="keruisiya"; console.log(stu1.name+"=="+stu1.age+"=="+stu1.arr+"=="+stu1.score) console.log(stu2.name+"=="+stu2.age+"=="+stu1.arr+"=="+stu2.score) stu1.arr[2]=10; console.log(stu1.name+"=="+stu1.age+"=="+stu1.arr+"=="+stu1.score) //arr同时改变 console.log(stu2.name+"=="+stu2.age+"=="+stu1.arr+"=="+stu2.score)
-
组合继承(结合上序两种方法)
function Person(name,age){ this.name=name; this.age=age; this.getage=function () { console.log(this.age); } } Person.prototype.sayHi=function () { console.log("hahaha"); } function Student(name,age,score){ //借用构造函数:属性值重复的问题 Person.call(this,name,age); this.score=score; } //改变原型指向----继承 Student.prototype=new Person();//不传值(用来改变 指向的 )
也还是有缺点的,因为 Person被执行了两次 ---------先就这样掌握就好
-
组合继承优化
参考网址: http://www.imooc.com/article/20162
-
ES6
中继承 -----具体细节 等到es6
中再提;语法: *// 父类* class Father{ } *// 子类继承父类* class Son extends Father { } 举例: class Parent { constructor(name,age) { this.name=name; this.age=age; } say(){ console.log('我是你的爸爸!'); } } class Child1 extends Parent { constructor(name, age, colors) { super(name, age); // 调用父类的constructor(name, age) this.colors = colors; } toString() { return this.colors + ' ' + super.say(); // 调用父类的say() } }
-
-
从原型链看继承