面向对象(ES5与ES6类的继承解析)

来源:http://www.chinese-glasses.com 作者:Web前端 人气:171 发布时间:2020-04-15
摘要:任何函数都有一个prototype属性,这个属性称为函数的“原型”,属性值是一个对象。 只有函数有原型属性 。 ES5 面向对象 函数体内部使用了this关键字,代表了所要生成的对象实例。生

任何函数都有一个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属性,指向其对应的构造函数

构造函数、实例、原型三者关系如下图:

图片 1

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 属性的作用

多重继承

  1. 覆盖原型对象

    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,需手动改变。

  1. 在构造函数内部通过 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多少个实例,方法将会被复制多少次,虽然复制出来的函数是一样的,但分别指向不同的引用地址,不利于函数的复用。

  1. 构造函数内部使用严格模式

强制使用 new 命令

function Person(name){ this.name = name;}

生产环境下,我们可以使用Object.getPrototypeOf(obj)方法来获取参数对象的原型。

Object.getPrototypeOf(obj)

本文由10bet发布于Web前端,转载请注明出处:面向对象(ES5与ES6类的继承解析)

关键词:

上一篇:整理经常在H5移动端开发遇到的知识

下一篇:没有了

最火资讯