7.1 面向对象的特性 – 继承
51.PNG编写类的过程是一个封装的过程
继承:1重复利用一些代码(对代码的复用) 2.继承是多态的前提
52.PNG
7.2 Object的原型的原型是顶层原型
54.PNG7.3 Object是所有类的父类
55.PNGObject的原型对象
特殊一:该对象有原型属性,但是它的原型属性已经指向的是null,也就是已经是顶层原型了;
特殊二:该对象上有很多默认的属性和方法;
7.4 为什么需要继承
// Student
function Student(name, age, sno) {
this.name = name
this.age = age
this.sno = sno
}
Student.prototype.running = function() {
console.log(this.name + " running~")
}
Student.prototype.eating = function() {
console.log(this.name + " eating~")
}
Student.prototype.studying = function() {
console.log(this.name + " studying")
}
// Teacher
function Teacher(name, age, title) {
this.name = name
this.age = age
this.title = title
}
Teacher.prototype.running = function() {
console.log(this.name + " running~")
}
Teacher.prototype.eating = function() {
console.log(this.name + " eating~")
}
Teacher.prototype.teaching = function() {
console.log(this.name + " teaching")
}
这两个类的封装有很多重复的代码。属性name ,age
function running, function eating
我们想着是不是可以把这些公共的属性和代码都封装到一个父类里面,然后student和teacher(子类)再继承它,然后封装自己特有的属性和方法。
7.5 继承-使用原型链的继承方案
// 父类: 公共属性和方法
function Person() {
this.name = "why";
//this.friends = []
}
Person.prototype.eating = function () {
console.log(this.name + " eating~");
};
// 子类: 特有属性和方法
function Student() {
this.sno = 111;
}
var p = new Person();
Student.prototype = p;
Student.prototype.studying = function () {
console.log(this.name + " studying~");
};
// name/sno
var stu = new Student();
console.log(Student.prototype.constructor === Person); //true
/*
注意:这个时候Student.prototype.constructor是指向Person的,因为Student.Prototype里面是p对象,p对象没有constructor这个属性,
所以原型链查找,找到p.__proto__也就是Person.prototype,这个里面有constructor,指向Person
*/
// console.log(stu.name)
// stu.eating()
// stu.studying()
56.PNG
缺点:
// 原型链实现继承的弊端:
// 1.第一个弊端: 打印stu对象, 继承的属性是看不到的
// console.log(stu) 可以打印出来,但是直接看不到,只能看到sno:11 和[[prototype]]这两个属性
// 2.第二个弊端: 创建出来两个stu的对象
var stu1 = new Student()
var stu2 = new Student()
如果给Person里面添加属性this.friends = [],
这样stu1有个frend,stu2有个friend
他们两个应该是相互独立的
如果给stu1.friend添加了一个‘kobe’
// 获取引用, 修改引用中的值, 会相互影响
stu1.friends.push("kobe")
再打印stu2.frined的话,结果是["kobe"],stu2.friend也被修改了
console.log(stu1.friends)
console.log(stu2.friends)
// 直接修改对象上的属性, 相当于是给本对象添加了一个新属性,不会互相影响
stu1.name = "kobe"
console.log(stu2.name)//"why"
再这里,stu1的name属性是被新地添加的,是属于自己的属性,
但是stu2.name里面的name属性是使用的原型上面的name属性,也就是p对象上的name
// 3.第三个弊端: 在前面实现类的过程中都没有传递参数
var stu3 = new Student("lilei", 112)
“lilei”的处理应该是放到Person里面的,所以将参数传给student构造函数时不好处理的,
所以要考虑怎么把"lilei",也就是name这个参数传递给Perison构造函数
总结:
原型链继承,将父类的实例作为子类的原型,
优点:父类新增的原型方法/属性,子类都能够访问,并且原型链继承简单易于实现,
但是目前有一个很大的弊端:某些属性其实是保存在父亲对象上的;
第一,我们通过直接打印子类的实例对象是看不到这个属性的;
第二,子类的原型的属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题;
第三,不能给Person传递参数,因为这个对象是一次性创建的(没办法定制化);
7.6 继承-借用构造函数(父构造函数)方案
57.PNG58.PNG
// 父类: 公共属性和方法
function Person(name, age, friends) {
// this = stu
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function() {
console.log(this.name + " eating~")
}
// 子类: 特有属性和方法
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends)//把Person当成一个普通的函数使用
// this.name = name
// this.age = age
// this.friends = friends
this.sno = 111
}
var p = new Person()
Student.prototype = p
Student.prototype.studying = function() {
console.log(this.name + " studying~")
}
// name/sno
var stu = new Student("why", 18, ["kobe"], 111)
// console.log(stu.name)
// stu.eating()
// stu.studying()
弊端:
// 原型链实现继承已经解决的弊端
// 1.第一个弊端: 打印stu对象, 继承的属性是看不到的===>解决,可以看到
console.log(stu)
//Student {name: 'why', age: 18, friends: Array(1), sno: 111}
// 2.第二个弊端: 创建出来两个stu的对象
var stu1 = new Student("why", 18, ["lilei"], 111)
var stu2 = new Student("kobe", 30, ["james"], 112)
// // 获取引用, 修改引用中的值, 会相互影响===>不会相互影响
stu1.friends.push("lucy")
console.log(stu1.friends)//(2) ['lilei', 'lucy']
console.log(stu2.friends)//['james']
// // 3.第三个弊端: 在前面实现类的过程中都没有传递参数===》解决:可以传递参数
// var stu3 = new Student("lilei", 112)
// 强调: 借用构造函数也是有弊端:
// 1.第一个弊端: Person函数至少被调用了两次
var p = new Person()的时候调用了一次,new Student的时候又调用了一次
// 2.第二个弊端: stu的原型对象(p对象)上会多出一些属性, 但是这些属性是没有存在的必要。==》name,friends,age都是undefined
7.7 继承-对象的原型式继承
实现的式一个对象继承另外一个对象
var obj = {
name:"why",
age:18
}
//目的:创建一个对象,让这个对象的原型指向obj
//原型式继承函数实现1
function createObject1(o) {
var newObj = {};
//Object.getPrototypeOf
/* Object.setPrototypeOf() 方法设置一个指定的对象的原型(即,内部 [[Prototype]] 属性)到另一个对象或 null。 */
Object.setPrototypeOf(newObj, o);
return newObj;
}
var info = createObject1(obj);
console.log(info);
console.log(info.__proto__);
console.log(info.__proto__ === obj); //true
//原型式继承函数实现2
function createObject2(o){
function Fn(){}
Fn.prototype = o;
var newObj = new Fn()
return newObj
}
//为什么不这么写
/*
var newObj = {};
newObj.__proto__ = o;
*/
//我们使用__proto__是为了打印,更好的看到原型对象
//真实开发里面是不会操作__proto__
var info2 = createObject2(obj);
console.log(info2);
console.log(info2.__proto__);
console.log(info2.__proto__ === obj) //true
//原型式继承函数实现3
//直接使用Object.create方法
//Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型--proto--。
var info3 = Object.create(obj);
console.log(info3);
console.log(info3.__proto__);
实际上我们想实现的式一个构造函数继承另一个构造函数,比如new Student()继承 new Person(),来实现代码的复用
7.8 继承-寄生式继承
59.PNG
var personObj = {
running: function(){
console.log("running");
}
}
function createStudent(name){
var stu = Object.create(personObj);
stu.name = name;
stu.studying = function(){
console.log("studying");
}
return stu
}
var stuObj = createStudent("why")
var stuObj1 = createStudent("kobe")
var stuObj2 = createStudent("james")
console.log(stuObj);
//工厂函数创建的,弊端很明确,不知道对象的类型
7.9 继承-寄生组合式继承
function Person(name, age, friends){
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.running = function(){
console.log("running");
}
Person.prototype.eating = function(){
console.log("eating");
}
function Student(name, age, friends, sno, score){
Person.call(this, name, age, friends);
this.sno = sno;
this.score = score;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.studying = function(){
console.log("studying");
}
var stu = new Student("why", 18, ["kobe"], 111, 100)
console.log(stu);//Student {name: 'why', age: 18, friends: Array(1), sno: 111, score: 100}
console.log(Object.getPrototypeOf(stu));//Person {studying: ƒ}
console.log(stu.constructor.name);//Person
node 打印结果
Person {
name: 'why',
age: 18,
friends: [ 'kobe' ],
sno: 111,
score: 100
}
Person { studying: [Function (anonymous)] }
Person
在这里,node和浏览器打印的stu的类型不一样,是因为浏览器做了特殊处理,让他显示成立Student,方便做调试,浏览器再调试工作中经常做类似的优化。
Object.getPrototypeOf(obj)
* 返回指定对象的原型(内部[[Prototype]]属性的值)。
* obj:要返回其原型的对象。
* 返回值:给定对象的原型。如果没有继承属性,则返回 null 。
node打印的类型就是stu.constructor.name。在这里,就是Person这个构造函数
在这里,stu的原型对象的值,就是stu.__proto
是指向Person的原型的,
console.log(stu.__proto__ === Object.getPrototypeOf(stu));//true
这样子做的话stu的类型还是Person,因为stu.constructor.name是Person
我们可以通过下面的方法解决,让Student的原型的constructor指向Student构造函数本身
function Person(name, age, friends){
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.running = function(){
console.log("running");
}
Person.prototype.eating = function(){
console.log("eating");
}
function Student(name, age, friends, sno, score){
Person.call(this, name, age, friends);
this.sno = sno;
this.score = score;
}
Student.prototype = Object.create(Person.prototype);
Object.defineProperty(Student.prototype, "constructor",{
enumerable:false,
configurable:true,
writable:true,
value:Student
})
Student.prototype.studying = function(){
console.log("studying");
}
var stu = new Student("why", 18, ["kobe"], 111, 100)
console.log(stu);
console.log(Object.getPrototypeOf(stu));
console.log(stu.constructor.name);
console.log(stu.__proto__ === Object.getPrototypeOf(stu));
console.log(stu.__proto__);
console.log(Student.prototype);
打印结果
Student {
name: 'why',
age: 18,
friends: [ 'kobe' ],
sno: 111,
score: 100
}
Person { studying: [Function (anonymous)] }
Student
true
Person { studying: [Function (anonymous)] }
现在student继承自person,以后想让teacher继承person就必须重新写,所以封装一下比较好。
/*
Object.create 是比较新的函数,
大部分人会使用下面自己写的函数
*/
function createObject(o){
function Fn(){}
Fn.prototype = o;
return new Fn()
}
function inheritPrototype(SubType, SuperType) {
SubType.prototype = Object.create(SubType.prototype);
Object.defineProperty(SuperType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person(name, age, friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.running = function () {
console.log("running");
}
Person.prototype.eating = function () {
console.log("eating");
}
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends);
this.sno = sno;
this.score = score;
}
/* Student.prototype = Object.create(Person.prototype);
Object.defineProperty(Student.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: Student
})
*/
inheritPrototype(Student, Person)
Student.prototype.studying = function () {
console.log("studying");
}
var stu = new Student("why", 18, ["kobe"], 111, 100)
console.log(stu);
console.log(Object.getPrototypeOf(stu));
console.log(stu.constructor.name);
console.log(stu.__proto__ === Object.getPrototypeOf(stu));
console.log(stu.__proto__);
console.log(Student.prototype);
打印结果
Student {
name: 'why',
age: 18,
friends: [ 'kobe' ],
sno: 111,
score: 100
}
Person { studying: [Function (anonymous)] }
Student
true
Person { studying: [Function (anonymous)] }
Person { studying: [Function (anonymous)] }
7.10 对象的方法补充
60.PNG1. Object.create()
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj, {
address: {
value: "北京市",
enumerable: true
}
})
console.log(info);
console.log(info.__proto__);
//{address: '北京市'}
//test.html:65 {name: 'why', age: 18}
create后面可以选择两个参数,第一个是创建出来的新对象的原型,第二个是向往新对象里传入的属性。
2. Object.hasOwnProperty()
hasOwnProperty方法判断:
对象是否有某一个属于自己的属性(不是在原型上的属性)
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj, {
address: {
value: "北京市",
enumerable: true
}
})
console.log(info);
console.log(info.__proto__);
//其中
console.log(info.address);
console.log(info.name);
console.log(info.age);
//都可以打印出来,但是address是info自己的属性,name和age是原型对象的属性
//怎么判断哪个是不是真的自己的呢
// hasOwnProperty方法判断(p 对象是否有某一个属于自己的属性(不是在原型上的属性))
console.log(info.hasOwnProperty("address"))//true
console.log(info.hasOwnProperty("age"))//false
console.log(info.hasOwnProperty("name"))//false
3. in操作符
in 操作符: 不管在当前对象还是原型中返回的都是true
//in 操作符
console.log("address" in info)//true
console.log("name" in info)//true
console.log("age" in info)//true
3.for in 遍历
获取对象上面的所有属性
for (var key in info){
console.log(key);
console.log(info[key]);
}
//address name age
4.instanceof
用于检测构造函数的pototype,是否出现在某个实例对象的原型链上
function createObject(o) {
function Fn() {}
Fn.prototype = o
return new Fn()
}
function inheritPrototype(SubType, SuperType) {
SubType.prototype = createObject(SuperType.prototype)
Object.defineProperty(SubType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person() {
}
function Student() {
}
inheritPrototype(Student, Person)
console.log(Person.prototype.__proto__)
var stu = new Student()
console.log(stu instanceof Student) // true
console.log(stu instanceof Person) // true
console.log(stu instanceof Object) // true
5.isPrototypeOf
用于检测某个对象,是否出现在某个实例对象的原型链上
function Person() {
}
var p = new Person()
console.log(p instanceof Person) //true
console.log(Person.prototype.isPrototypeOf(p))//true
//Person.prototype有没有出现在p的原型链上
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj)
// console.log(info instanceof obj)//instanceof 后面只能是构造函数
console.log(obj.isPrototypeOf(info))//true
7.11 **原型继承关系
61.PNG 62.PNG 63.PNG//Function 也是一个function,也有作为对象的属性,有__proto__属性
//任何一个函数的__proto__属性都是指向Function.prototype的
//Function 也是由 new Function()创建出来的
//就像是普通函数 Foo.__proto__ = Function.prototype
console.log(Function.prototype === Function.__proto__)//true
网友评论