1.面向对象的补充
1.1 枚举属性的补充
- 不可枚举属性在node环境下,不会被打印出来
- 但是如果是浏览器中会将其显示出来,但是颜色会呈灰色,这是为了让开发者更好的去调试
var obj={
name:"wjy",
age:20
}
Object.defineProperty(obj,"address",{
value:"怀化",
enumerable:false,
writable:true,
configurable:true
})
console.log(obj);
node环境下:
浏览器下:
2.JavaScript中的类和对象
当我们编写如下代码的时候,我们会如何来称呼这个Person呢?
- 在JS中Person应该被称之为是一个构造函数
- 从很多面向对象语言过来的开发者,也习惯称之为类,因为类可以帮助我们创建出来对象p1,p2
- 如果从面向对象的编程范式角度来看,Person确实是可以称之为类的
function Person(){
}
var p1=new Person()
var p2=new Person()
- 从严格模式下,这并不能称之为是一个类,
3.面向对象的特性-继承
面向2对象有三大特性:封装、继承、多态
- 封装:我们前面蒋属性和方法封装到一个类中,可以称之为封装的过程
- 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中)
- 多态:不同的对象在执行时表现出不同的形态
那么继承是做什么呢?
- 继承可以帮我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可。
那么javascript当中是如何实现继承呢?
- 不着急,我们先来看一下JavaScript原型链的机制
- 再利用原型链的机制实现一下继承:
4.JavaScript原型链
在真正实现继承之前,我们需要了解一个非常重要的概念:原型链。
我们知道,从一个对象上获取属性,如果在当前对象中没有找到,会去原型中找,如果原型还没找到,会去原型的原型中查找,如果还没找到,去原型的原型的原型中查找,直到到最顶层的对象,如果还没有找到,则会返回一个undefined
在查找的过程中其实会沿着原型链去查找
var obj={
name:"wjy",
age:20
}
obj.__proto__={
};
obj.__proto__.__proto__={
};
obj.__proto__.__proto__.__proto__={
address:"怀化"
}
console.log(obj.address); //怀化
5.Object的原型
那什么会是原型链的尽头呢?比如第三个对象是否也有原型[[prototype]]属性呢?
console.log(obj.__proto__.__proto__.__proto__.__proto__);
-
我们会发现它打印的是 [Object: null prototype] {}
- 事实上它已经是顶层对象了
- 从Object直接创建出来的对象的原型都是[Object: null prototype] {}
-
那么我们可能会问:[Object: null prototype] {}到底有什么特殊的
- 特殊一:该对象有原型属性,它的原型属性已经指向的是null,也就是已经是顶层原型了
- 特殊二:该对象上有很多默认的属性和方法
var obj={};//创建一个对象
var obj2=new Object();//创建一个对象
//* Object其实是一个构造函数,只不过这个构造函数,是js自己给我们创建好的,它也有原型对象,原型对象有个 constructor属性指向这个构造函数本身
// * 使用new关键字调用函数时会执行以下几个步骤
/**
* * 1 在内存中创建一个新的对象(空对象)
* * 2 这个新对象的[[prototype]]赋值为这个构造函数的prototype属性
* * 3 函数内部的this指向这个新对象
* * 4 执行函数内部的代码
* * 5 如果函数没有返回其他非空对象,则将新对象返回
*/
// * 所有直接通过Object创建出来的对象的原型都是[Object:null prototype]{}
//* 所以 obj.__proto__是指向Object.prototype
// * obj2.__proto__也是指向Object.prototype
console.log(obj.__proto__==Object.prototype);//true
console.log(obj2.__proto__==Object.prototype);//true
console.log(obj2.__proto__==obj2.__proto__);//true
//* 我们可以尝试将Object.prototype对象打印出来
// *之前我们讲过,如果一个原型对象的值为 [Object:null prototype]{}则代表这个对象是顶层对象,它的[[prototype]]属性的值为null
console.log(Object.prototype); //[Object: null prototype] {}
console.log(Object.prototype.__proto__); //null
// * 我们可以试着将这个Object.prototype对象的所有属性描述符打印出来,其实Object.prototype对象有很多属性,只不过是不可枚举的
console.log(Object.getOwnPropertyDescriptors(Object.prototype));
// * Object 是所有对象的最顶层的父类
6.Object是所有类的父类
从我们上面的Object原型我们可以得出一个结论:原型链的最顶层对象就是Object的原型对象
function Person(){
}
//* 查看Person的原型对象
console.log(Person.prototype);
console.log(Object.getOwnPropertyDescriptors(Person.prototype));
// * 查看Person的原型对象的__proto__
console.log(Person.prototype.__proto__);//* 它指向的是Object的原型对象 [Object: null prototype] {}
// * Object的原型对象的__proto__是指向null
console.log(Person.prototype.__proto__.__proto__);//* null
var p1=new Person()
7.为什么需要继承
-
学生构造函数
- 有以下属性
- name
- age
- sno
- 有以下方法
- eating
- running
- studying
- 有以下属性
-
老师构造函数
- 有以下属性
- name
- age
- title
- 有以下方法
- eating
- running
- studying
- teaching
- 有以下属性
// Student
function Student(name,age,sno){
this.name=name;
this.age=age;
this.sno=sno;
}
Student.prototype.eating=function(){
console.log(this.name+" eatings");
}
Student.prototype.running=function(){
console.log(this.name+" running");
}
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.eating=function(){
console.log(this.name+" eatings");
}
Teacher.prototype.running=function(){
console.log(this.name+" running");
}
Teacher.prototype.studying=function(){
console.log(this.name+" studying");
}
Teacher.prototype.teaching=function(){
console.log(this.name+" teaching");
}
我们仔细一看会发现 存在大量的重复代码,
比如说 重复的属性:name、age
比如说:重复的方法:eating、running、studying
我们是否可以将这些共有的属性和方法抽象到一个父类中?
// * 父类 :放公共的属性和方法
function Person(){
this.name="why";
}
Person.prototype.eating=function(){
console.log(this.name+"在吃东西");
}
function Student(){
this.sno=111;
}
Student.prototype.studying=function(){
console.log(this.name+"在学习");
}
var stu=new Student();
console.log(stu.name);
console.log(stu.eating);
- 以上这两个类没有关联.所以stu.eating会是undefined
7.1 继承-原型链的继承方案
// * 父类 :放公共的属性和方法
function Person(){
this.name="why";
}
Person.prototype.eating=function(){
console.log(this.name+"在吃东西");
}
function Student(){
this.sno=111;
}
// * 通过原型链的方式实现了继承,将Student的原型对象赋值为Person函数的实例对象
var p=new Person()
Student.prototype=p;
Student.prototype.studying=function(){
console.log(this.name+"在学习");
};
var stu=new Student();
console.log(stu.name);
console.log(stu.eating());
7.1.1 缺点
打印stu,有些属性是看不到的,因为继承的属性是看不到的,(原型上的属性或方法是不能直接看到的)
-
如果创建两个对象,两个对象的部分属性不是相互独立的
- 如果是直接修改对象属性的值,这个属性的值是独立的
- 如果是获取对象属性的引用,修改引用中的值,这个属性的值不是独立的,会相互影响的
在前面实现类的继承中,是没有传递参数的
7.1.1 缺点(官方术语)
某些属性是保存在p对象上的
- 我们通过直接打印对象是看不到这个属性的
- 这个属性会被多个对象共享,如果这个属性是引用类型,那么就会造成问题
- 不能给Person传递参数,因为这个对象是一次性的(不能实现定制化)
7.2 继承——借用构造函数方案
为了解决原型链继承中存在的问题,开发人员提供了一种新的技术:constructor stealing(有很多名称:借用构造函数或者称之为经典继承或者称之为伪造对象)
- steal是偷窃、剽窃的意思,但是这里可以翻译成借用;
借用继承的做法非常简单:在子类型构造函数内部调用父类型构造函数
- 因为函数可以在任意时刻被调用
- 因此通过apply()和call()方法也可以在新创建的对象执行构造函数
7.2.1 实现可定制化(可以传参)
// * 父类 :放公共的属性和方法
function Person(name,age,friend){
this.name=name;
this.age=age;
this.friend=friend;
}
Person.prototype.eating=function(){
console.log(this.name+"在吃东西");
}
function Student(name,age,friend,sno){
Person.call(this,name,age,friend)
this.sno=sno;
}
//* 1.实现传参
var stu=new Student("wjy",20,['hyz','zwl'],111)
console.log(stu);
7.2.2 创建的多个对象之间的数据是独立的,不会相互影响
// * 父类 :放公共的属性和方法
function Person(name,age,friend){
this.name=name;
this.age=age;
this.friend=friend;
}
Person.prototype.eating=function(){
console.log(this.name+"在吃东西");
}
function Student(name,age,friend,sno){
Person.call(this,name,age,friend)
this.sno=sno;
}
//* 1.实现传参
var stu=new Student("wjy",20,['hyz','zwl'],111)
console.log(stu);
// * 2.创建的两个对象不会相互影响
var stu1=new Student("hyz",21,['wjy','zmj'],222)
var stu2=new Student("zmj",21,['lt','txy'],333)
console.log(stu1);
console.log(stu2);
stu1.friend.push("zzz");
console.log(stu1);
console.log(stu2);
7.2.3 使用构造函数也是有弊端
- Person函数至少会调用两次
- stu的原型对象会多一些属性,但是这些属性是没有存在的必要。