任何函数都有一个prototype属性,这个属性称为函数的“原型”,属性值是一个对象。只有函数有原型属性。
ES5 面向对象
函数体内部使用了this关键字,代表了所要生成的对象实例。生成对象的时候,必须使用new命令。new 命令基本用法
面向对象的语言都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象,ES6之前并没有类的概念,在ES6中引入类class.
new.target
类的继承(两种方式)
一、原型链继承
对于什么是原型链?
每个构造函数都有一个原型对象,原型对象的constructor指向这个构造函数本身,而实例的__proto__属性又指向原型对象。这个假设一个实例的__proto__内部指针指向其原型,而它的原型又是另一个类型的实例,那么它的原型又将指向另一个原型,另一个原型也包含一个指向它的构造函数的指针,假设另一个原型又是另一个类型的实例,这样层层递进,就构成了实例与原型的链条,这就是原型链的基本概念。
实现原型链的继承方式基本如下:
function Father () {
this.appearance = "beautiful"
}
Father.prototype.sayHappy = function () {
alert("快乐")
}
function Child () {
this.name= "Jhon"
}
Child.prototype= new Father() // 继承了父类的方法和属性
Child.prototype.addArr= [1,2,3,4,5]
var child= new Child()
child.sayHappy() // 弹出“快乐”
child.appearance // "beautiful"
child.addArr // [1,2,3,4,5]
原型链继承的缺点:① 不能传参 ② 若原型上的方法时引用类型的话,不小心被修改了的话会影响其他实例。
二、借助构造函数继承(利用calll和apply改变this指针)
基本思路:在子类型构造函数的内部调用超类型的构造函数。
function Father (Hobby){
this.hobby= Hobby
}
Father.prototype.sayHappy = function () {
alert("快乐")
}
function Child () {
this.name= "Jhon"
Father.call(this,"Play Games") // 或者Father.apply(this,["Play Games"]),继承了Father的属性和方法
}
var child = new Child()
child.sayHappy //
没有反应,原型上的方法和属性不会继承
child.hobby // "Play Games"
借助构造函数继承的缺点:① 方法都在构造函数中定义,函数的复用无从谈起 ② 超类中的方法对子类不可见。
三、组合继承(也叫经典继承,将原型链和借助构造函数继承相结合)
思路:1.原型链实现对原型属性和方法的继承;
2.构造函数实现对实例属性的继承,且调用基类的构造函数;
function Father(Hobby) {
this.hobby= Hobby;
this.exGF = ['cuihua', 'erya']
}
Father.prototype.sayHappy = function () {
alert("快乐")
}
function Child () {
this.name= "Jhon"
Father.call(this,"Play Games") // 或者Father.apply(this,["Play Games"]),继承了Father的属性和方法
}
Child.prototype= new Father()
Student.prototype.sayName= function () {
alert(this.name);
}
var liHua= new Child()
liHua.sayHappy()
liHua.sayName()
检测对象属性的两种方法:
object.hasOwnProperty(属性名),这个方法检测的是对象实例的属性(若是返回true),不能检测原型上的属性。
in操作符,检测对象所有的属性,包含原型和实例上的额,有的话就返回true.
判断一个原型是否在某个实例的原型链上:
Person.prototype.isPropotypeOf(personOne) // true
Object.prototype.isPropotypeOf(personOne) // true
判断一个构造函数是否在实例的原型链中出现过:
personOne instanceof Person // true
personOne instanceof Object // true
原型链机制
创建对象(四种模式简介,此外还有动态原型模式、寄生构造函数模式、稳妥构造函数模式等)
一、工厂模式
function createPerson (Name,Age,Job) {
var man= new Object();
man.name= Name;
man.age= Age;
man.job= Job;
man.sayName= function () {
alert(this.name)
}
return man;
}
var personOne= createPerson ("Erric",26,"Engineer");
var personTwo= createPerson ("Lori",26,"teacher");
优点:解决了多个相似对象的创建问题
缺点: ① 对象识别问题无法解决(即怎么知道一个对象的类型)
二、构造函数模式
function Person (Name,Age,Job) {
this.name = Name;
this.age = Age;
this.job= Job;
this.sayName= function () {
alert(this.name)
}
}
var personOne= new Person("Erric",26,"Engineer");
var personTwo= new Person("Lori",26,"teacher");
注一: 若不使用new操作符直接调用函数,那么其属性和方法都会被添加到window对象里面(因为在全局作用域调用一个方法时,this总是指向window对象)
如: Person("Erric",26,"Enginee")
window.sayName() // 弹出 "Erric"
window.name // "Erric"
window.age // 26
注二: new 操作符实际上进行了以下操作
① 创建一个新的对象
② 将构造函数的作用域赋给新对象(this指向了这个新的对象)
③ 执行构造函数中的代码(为这个新对象添加属性)
④ 返回这个新的对象
优点:① 不用显式的创建对象
② 将属性和方法赋给了this对象
③ 没有return语句
缺点:① 每个方法都要在每个实例上重新创建一遍(personOne和personTwo中的sayName方法不是同一个方法,每个函数都是一个对象,故每 定义了一个函数就实例化了一个对象)。
此问题也可以通过将方法单独抽出来解决(但是方法一多,都移到全局的话封装性就无从谈起),如下:
function Person (Name,Age,Job) {
this.name = Name;
this.age = Age;
this.job= Job;
this.sayName= sayName
}
function sayName() {
alert(this.name)
}
var personOne= new Person("Erric",26,"Engineer");
var personTwo= new Person("Lori",26,"teacher");
② 若是将公共的sayName方法移到全局,那么又没有封装性可言了。
三、原型模式
function Person () {
}
Person.prototype.name= "Erric"
Person.prototype.age= "28"
Person.prototype.job= "Job"
Person.prototype.sayName= function () {
alert(this.sayName)
}
优点:① 解决了函数共用的问题,不用每个实例都创建一遍方法。
缺点:① 不能传参
② 如果实例中修改了原型中的属性(引用类型)或方法,那么这个属性或方法会被彻底的修改,而影响到其他实例。
四、构造函数+原型组合模式
function Person (Name,Age,Job) {
this.name= Name
this.age= Age
this.job= Job
}
Person.prototype.sayName= function () {
alert(this.name)
}
// 上面往原型上添加属性和方法的也可如下写,但是此时原型的constructor不指向Person构造函数,而是指向Object,因为Person.prototype就像一个新的对象实例,它的__proto__指向Object原型。
// Person.prototype= {
constructor: Person, // 重新再实例中定义constructor的指向,覆盖Object原型中的constructor指向
sayName: function () {
alert(this.name)
}
}
var personOne= new Person("Erric",26,"Engineer");
var personTwo= new Person("Lori",26,"teacher");
原型对象的理解(重要)
1.首先得明白以下三点:
① 每个函数(含构造函数)都有一个prototype属性,指向Person原型
② 每个实例都有一个__proto__属性,也指向Person原型
③ 每个原型都有一个constructor属性,指向其对应的构造函数
构造函数、实例、原型三者关系如下图:
2.万物皆对象,说明原型链的最开始点都是Object,所以任何一个引用类型的 instanceof Object都会返回true。
let o1 = {};let o2 = Object.create(o1);let o3 = Object.create(o2);o2.isPrototypeOf(o3) // trueo1.isPrototypeOf(o3) // trueo2.isPrototypeOf(o2) // false
ES6 面向对象
ES6中引入了Class(类)这个概念,通过关键字class可以创建一个类。类的数据类型就是函数,类的所有方法都定义在prototype属性上。
class Person () {
constructor (x,y) {
this.name= x
this.age= y
}
sayName () {
alert("快乐")
}
}
var liHua= new Person("张俊泽",26)
注: 可以理解为constuctor中的属性和方法为ES5中的构造函数部分,和constructor同级的是ES5中原型上的方法和属性。
ES6的继承通过extends关键字实现
class Father(){}
class Child extends Father {
constructor(x,y,color){
super(x,y)
this.color= color
}
toString() {
retunr "世界和平!"
}
}
上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
类的prototype和__proto__属性
Class作为构造函数的语法唐,同时有prototype和__proto__属性,因此存在两条继承链:
① 子类的__proto__,表示构造函数的继承,总是指向父类
② 子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class Father {
}
class Child extends Father{
constructor () {
super()
}
}
var childOne= new Child()
Child.__proto__ == Father // true
childOne.__proto__ == Child.prototype // true
Child.prototype.__proto__ == Fahter.prototype // true
Person.prototype.sayHi = function(){ console.log('Hi,I am Dora');}
instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。继承的子类实例也是父类的实例,因此继承的也为true。
Object.prototype.isPrototypeOf()
constructor 属性
实例验证instanceof 运算符
首先继承父类的属性
Sub.prototype.funName = function(){ Super.prototype.funName.call(this); // some other code}
任何一个对象都有__proto__属性,这个属性称为对象的“原型对象”,一个对象的原型对象就是它的构造函数的prototype。
__proto__并不是语言本身的属性,这是各大浏览器厂商添加的私有属性,虽然目前很多浏览器都可以识别这个属性,但依旧不建议在生产环境下使用,避免对环境产生依赖。
prototype 属性的作用
多重继承
覆盖原型对象
Person.prototype = { sayHi: function(){ console.log('Hi,I am Dora'); }}
当访问对象的属性时,如果这个对象没有这个属性,系统就会查找这个对象的__proto__原型对象,原型对象也是个对象,也有自己的__proto__原型对象,然后就会按照这个原型链依次往上查找,直到原型链的终点Object.prototype。
function Person(){}let p1 = new Person();p1.__proto__ === Person.prototype // true
创建一个空对象,作为将要返回的对象实例。let o = new Object();将这个空对象的原型,指向构造函数的prototype属性。Object.setPrototypeOf(o,Foo.prototype);将构造函数的 this 绑定到新创建的空对象上。Foo.call(o);始执行构造函数内部的代码。
Person.prototype = { constructor: Person, sayHi: function(){ console.log('Hi,I am Dora'); }}
function f(){}typeof f.prototype // "object"
let a = new Person('dora');a.name // dora
时间: 2019-10-04阅读: 116标签: 继承构造函数
报错原因是因为不加new调用构造函数时,this指向全局对象,而严格模式下,this不能指向全局对象,默认等于undefined,给undefined添加属性肯定会报错。
其次继承父类的方法
Object()是系统内置的构造函数,用来创建对象的,Object.prototype是所有对象的原型链顶端,而Object.prototype的原型对象是null。
如果当前函数是new命令调用的,在函数内部的new.target属性指向当前函数,否则为undefined。
使用new命令时,它后面的函数依次执行下面的步骤:
此时子类实例的constructor指向父类构造函数Super,需手动改变。
在构造函数内部通过 instanceof 判断是否使用 new 命令
function Person(name, age) { if (!(this instanceof Person)) { return new Person(name, age); } this.name = name; this.age = age;}Person('dora', 18).name // dora(new Person('dora', 18)).age // 18
单个方法的继承
Sub.prototype = Object.create(Super.prototype);// orSub.prototype = new Super();
let d = new Date();d instanceof Date // trued instanceof Object // true
如果将对象的方法写入构造函数中,则new多少个实例,方法将会被复制多少次,虽然复制出来的函数是一样的,但分别指向不同的引用地址,不利于函数的复用。
- 构造函数内部使用严格模式
强制使用 new 命令
function Person(name){ this.name = name;}
生产环境下,我们可以使用Object.getPrototypeOf(obj)方法来获取参数对象的原型。
Object.getPrototypeOf(obj)
本文由10bet发布于Web前端,转载请注明出处:面向对象(ES5与ES6类的继承解析)
关键词: