JavaScript ES5中实现继承

1 对象和函数的原型

2 new、constructor

3 原型链的查找顺序

4 原型链实现的继承

5 借用构造函数继承

6 寄生组合实现继承

function 创建的名称如果开头是大写的,那这个创建的不是函数,是创建了类。

要注意区分!本章很多这样子的类。

实例有隐式的原型,声明的函数或者对象有显示和隐式的原型。显式原型里面有constructor和隐式原型。 各对象间的关系如下图。

普通对象的原型

对象的原型就是下图的Prototype,一般会被隐藏

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>

    var obj = {
      name: "why",
      age: 18
    }
    console.log(obj)

    var info = {}

    // 获取对象的原型
    console.log(obj.name, obj.age)
    console.log(obj.__proto__)
    console.log(Object.getPrototypeOf(obj))
    console.log(obj.__proto__ === Object.getPrototypeOf(obj)) // true

    // 疑问: 这个原型有什么用呢?
    // 当我们通过[[get]]方式获取一个属性对应的value时
    // 1> 它会优先在自己的对象中查找, 如果找到直接返回
    // 2> 如果没有找到, 那么会在原型对象中查找
    console.log(obj.name)

    // 在原型中添加一个参数,开发时候不建议这样子用
    obj.__proto__.message = "Hello World"
    console.log(obj.message)

  </script>

</body>
</html>

 函数对象的原型

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>

    var obj = {}
    function foo() {}

    // 1.将函数看成是一个普通的对象时, 它是具备__proto__(隐式原型)
    // 作用: 查找key对应的value时, 会找到原型身上
    // console.log(obj.__proto__)
    // console.log(foo.__proto__)


    // 2.将函数看成是一个函数时, 它是具备prototype(显式原型)
    // 作用: 用来构建对象时, 给对象设置隐式原型的
    console.log(foo.prototype)
    // console.log(obj.prototype) 对象是没有prototype

  </script>

</body>
</html>

new操作原型赋值

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>

    function Foo() {
      // 1.创建空的对象
      // 2.将Foo的prototype原型(显式隐式)赋值给空的对象的__proto__(隐式原型)
    }

    console.log(Foo.prototype)

    var f1 = new Foo()
    var f2 = new Foo()
    var f3 = new Foo()
    var f4 = new Foo()
    var f5 = new Foo()
    console.log(f1.__proto__)
    console.log(f1.__proto__ === Foo.prototype) // true
    console.log(f3.__proto__ === f5.__proto__) // true

  </script>

</body>
</html>

将方法放在原型上 

new操作的作用很重要。注意:定义的一个函数如果里面有几个函数,通过new来创建函数的话,就会创建函数个数*定义的函数里面的函数个数。这些函数很多重复的,浪费空间;所以使用原型的办法来作为应对的办法。

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>

    /*
    1.什么是函数的显式原型
      * 区分和对象原型区别
    2.函数的原型的作用
      * 在通过new操作创建对象时, 将这个显式原型赋值给创建出来对象的隐式原型
    3.案例Person, 将所有的函数定义放到了显式原型上
    */

    function Student(name, age, sno) {
      this.name = name
      this.age = age
      this.sno = sno

      // 1.方式一: 编写函数, 会创建很多个函数对象
      // this.running = function() {
      //   console.log(this.name + " running")
      // }
      // this.eating = function() {
      //   console.log(this.name + " eating")
      // }
      // this.studying = function() {
      //   console.log(this.name + " studying")
      // }
    }

    // 当我们多个对象拥有共同的值时, 我们可以将它放到构造函数对象的显式原型
    // 由构造函数创建出来的所有对象, 都会共享这些属性
    Student.prototype.running = function() {
      console.log(this.name + " running")
    }
    Student.prototype.eating = function() {
      console.log(this.name + " eating")
    }

    // 1.创建三个学生
    var stu1 = new Student("why", 18, 111)
    var stu2 = new Student("kobe", 30, 112)
    var stu3 = new Student("james", 18, 111)

    // 隐式原型的作用
    // 1> stu1的隐式原型是谁? Student.prototype对象
    // 2> stu1.running查找:
    //  * 先在自己身上查找, 没有找到
    //  * 去原型去查找
    stu1.running()
    stu2.eating()

  </script>

</body>
</html>

显式原型中的属性

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>
    // 非常重要的属性: constructor, 指向Person函数对象
    function Person() {

    }

    // 1.对constructor在prototype上的验证
    var PersonPrototype = Person.prototype
    console.log(PersonPrototype)
    console.log(PersonPrototype.constructor)
    console.log(PersonPrototype.constructor === Person)

    console.log(Person.name)
    console.log(PersonPrototype.constructor.name)

    // 2.实例对象p
    var p = new Person()
    console.log(p.__proto__.constructor)
    console.log(p.__proto__.constructor.name)

  </script>

</body>
</html>

创建对象过程内存,具体内存图看ppt

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>

    function Person(name, age) {
      this.name = name
      this.age = age
    }

    Person.prototype.running = function() {
      console.log("running~")
    }

    var p1 = new Person("why", 18)
    var p2 = new Person("kobe", 30)

    // 进行操作
    console.log(p1.name)
    console.log(p2.name)

    p1.running()
    p2.running()

    // 新增属性
    Person.prototype.address = "中国"
    p1.__proto__.info = "中国很美丽!"

    p1.height = 1.88
    p2.isAdmin = true

    // 获取属性
    console.log(p1.address)
    console.log(p2.isAdmin)
    console.log(p1.isAdmin)
    console.log(p2.info)

    // 修改address
    p1.address = "广州市"
    console.log(p2.address)

  </script>

</body>
</html>

重写函数原型对象

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>

    function Person() {

    }

    console.log(Person.prototype)
    
    // 在原有的原型对象上添加新的属性
    // Person.prototype.message = "Hello Person"
    // Person.prototype.info = { name: "哈哈哈", age: 30 }
    // Person.prototype.running = function() {}
    // Person.prototype.eating = function() {}

    // console.log(Person.prototype)
    // console.log(Object.keys(Person.prototype))

    // 直接赋值一个新的原型对象,把原来的替换掉
    Person.prototype = {
      message: "Hello Person",
      info: { name: "哈哈哈", age: 30 },
      running: function() {},
      eating: function() {},
      // 加上constructor就和原本的原型差不多一样了
      // 但是原来的constructor是不可枚举的Object.keys枚举不到,所以还得改不可枚举
      // constructor: Person
    }
    // 通过下面对象属性描述符来给原型添加对应的constructor
    Object.defineProperty(Person.prototype, "constructor", {
      enumerable: false,
      configurable: true,
      writable: true,
      value: Person
    })

    console.log(Object.keys(Person.prototype))

    // 新建实例对象
    var p1 = new Person()
    console.log(p1.message)

  </script>

</body>
</html>

重要-对象的原型链

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>
    // 1.{}的本质
    // var info = {}
    // 相当于
    // var info = new Object()
    // console.log(info.__proto__ === Object.prototype)

    // 2.原型链
    var obj = {
      name: "why",
      age: 18
    }

    // 查找顺序
    // 1.obj上面查找
    // 2.obj.__proto__上面查找
    // 3.obj.__proto__.__proto__ -> null 上面查找(undefined)
    // console.log(obj.message)


    // 3.对现有代码进行改造
    obj.__proto__ = {
      // message: "Hello aaa"
    }

    obj.__proto__.__proto__ = {
      message: "Hello bbbb"
    }

    obj.__proto__.__proto__.__proto__ = {
      message: "Hello ccc"
    }

    console.log(obj.message)

  </script>

</body>
</html>

原型链实现方法的继承

这里讲的是是如何让student这个函数在自己的属性找不到、在原型什么找不到。想办法让student去person函数的原型上面找需要的属性。

错误方法一:将父类的原型直接给子类(不要这样子做)

 方式二:也不推荐,因为问题也挺多的,依赖父函数里面的属性,而且多个实例创建的属性会共用

 方式三借用构造函数属性继承,看下一个大标题的代码。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>

    // 定义Person构造函数(类)
    function Person(name, age, height, address) {
      this.name = name
      this.age = age
      this.height = height
      this.address = address
    }

    Person.prototype.running = function() {
      console.log("running~")
    }
    Person.prototype.eating = function() {
      console.log("eating~")
    }

    // 定义学生类
    function Student(name, age, height, address, sno, score) {
      this.name = name
      this.age = age
      this.height = height
      this.address = address

      this.sno = sno
      this.score = score
    }

    // 方式一: 父类的原型直接赋值给子类的原型
    // 缺点: 父类和子类共享通一个原型对象, 修改了任意一个, 另外一个也被修改
    // Student.prototype = Person.prototype

    // 方式二: 创建一个父类的实例对象(new Person()), 用这个实例对象来作为子类的原型对象
    var p = new Person("why", 18)
    Student.prototype = p

    // Student.prototype.running = function() {
    //   console.log("running~")
    // }
    // Student.prototype.eating = function() {
    //   console.log("eating~")
    // }
    Student.prototype.studying = function() {
      console.log("studying~")
    }

    // 创建学生
    var stu1 = new Student("kobe", 30, 111, 100)
    var stu2 = new Student("james", 25, 111, 100)
    stu1.running()
    stu1.studying()

    console.log(stu1.name, stu1.age)
    console.log(stu1)
    console.log(stu2.name, stu2.age)


  </script>

</body>
</html>

借用构造函数属性继承--重要

还是有缺点,

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>

    // 定义Person构造函数(类)
    function Person(name, age, height, address) {
      this.name = name
      this.age = age
      this.height = height
      this.address = address
    }

    Person.prototype.running = function() {
      console.log("running~")
    }
    Person.prototype.eating = function() {
      console.log("eating~")
    }

    // 定义学生类
    function Student(name, age, height, address, sno, score) {
      // 重点: 借用构造函数
      Person.call(this, name, age, height, address)
      // this.name = name
      // this.age = age
      // this.height = height
      // this.address = address

      this.sno = sno
      this.score = score
    }

    // 方式一: 父类的原型直接赋值给子类的原型
    // 缺点: 父类和子类共享通一个原型对象, 修改了任意一个, 另外一个也被修改
    // Student.prototype = Person.prototype

    // 方式二: 创建一个父类的实例对象(new Person()), 用这个实例对象来作为子类的原型对象
    var p = new Person("why", 18)
    Student.prototype = p

    // Student.prototype.running = function() {
    //   console.log("running~")
    // }
    // Student.prototype.eating = function() {
    //   console.log("eating~")
    // }
    Student.prototype.studying = function() {
      console.log("studying~")
    }

    // 创建学生
    var stu1 = new Student("kobe", 30, 111, 100)
    var stu2 = new Student("james", 25, 111, 100)
    stu1.running()
    stu1.studying()

    console.log(stu1.name, stu1.age)
    console.log(stu1)
    console.log(stu2.name, stu2.age)


  </script>

</body>
</html>

创建原型对象的方法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>
    // 工具函数
    // 创建对象的过程
    function createObject(o) {
      function F() {}
      F.prototype = o
      return new F()
    }

    // 将Subtype和Supertype联系在一起
    // 寄生式函数
    function inherit(Subtype, Supertype) {
      Subtype.prototype = createObject(Supertype.prototype)
      Object.defineProperty(Subtype.prototype, "constructor", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: Subtype
      })
    }

    /*
    满足什么条件:
      1.必须创建出来一个对象
      2.这个对象的隐式原型必须指向父类的显式原型
      3.将这个对象赋值给子类的显式原型
    */
    function Person(name, age, height) {}
    function Student() {}

    inherit(Student, Person)
    
    // 1.之前的做法: 但是不想要这种做法
    // var p = new Person()
    // Student.prototype = p

    // 2.方案一:
    var obj = {}
    // obj.__proto__ = Person.prototype
    Object.setPrototypeOf(obj, Person.prototype)
    Student.prototype = Person.prototype

    // 3.方案二:
    // function F() {}
    // F.prototype = Person.prototype
    // Student.prototype = new F()

    // 4.方案三:
    var obj = Object.create(Person.prototype)
    console.log(obj.__proto__ === Person.prototype)
    Student.prototype = obj

  </script>

</body>
</html>

最终继承的方案写法

创建一个工具inherit_utils.js文件,内容如下:

// 创建对象的过程
function createObject(o) {
  function F() {}
  F.prototype = o
  return new F()
}

// 将Subtype和Supertype联系在一起
// 寄生式函数
function inherit(Subtype, Supertype) {
  // Subtype.prototype.__proto__ = Supertype.prototype
  // Object.setPrototypeOf(Subtype.prototype, Subtype.prototype)
  Subtype.prototype = createObject(Supertype.prototype)
  Object.defineProperty(Subtype.prototype, "constructor", {
    enumerable: false,
    configurable: true,
    writable: true,
    value: Subtype
  })
  Object.setPrototypeOf(Subtype, Supertype)
  // Subtype.__proto__ = Supertype
}

在真正使用工具的地方引入工具:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script src="./js/inherit_utils.js"></script>
  <script>
    // 寄生组合式继承
    // 原型链/借用/原型式(对象之间)/寄生式函数
    function Person(name, age, height) {
      this.name = name
      this.age = age
      this.height = height
    }

    Person.prototype.running = function() {
      console.log("running~")
    }
    Person.prototype.eating = function() {
      console.log("eating~")
    }


    function Student(name, age, height, sno, score) {
      Person.call(this, name, age, height)
      this.sno = sno
      this.score = score
    }

    inherit(Student, Person)
    Student.prototype.studying = function() {
      console.log("studying")
    }

    // 创建实例对象
    var stu1 = new Student("why", 18, 1.88, 111, 100)

  </script>

</body>
</html>

ES5-原型-寄生式继承方案

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>
    // 对象之间的继承
    // 默认的对象
    var obj = {
      name: "why",
      age: 18
    }

    // 原型式继承
    function createObject(o) {
      function F() {}
      F.prototype = o
      return new F()
    }

    // 寄生式函数
    function createInfo(o, name, age, height, address) {
      var newObj = createObject(o)
      newObj.name = name
      newObj.age = age
      newObj.height = height
      newObj.address = address
      
      return newObj
    }

    // 创建另外一个对象, 这个对象可以继承自obj
    // var info1 = createObject(obj)  
    // info1.height = 1.88
    // info1.address = "广州市"

    // var info2 = createObject(obj)  
    // info1.height = 1.88
    // info1.address = "广州市"

    // var info3 = createObject(obj)  
    // info1.height = 1.88
    // info1.address = "广州市"

    // 创建一系列对象
    var info1 = createInfo(obj, "why", 18, 1.88, "广州市")
    var info2 = createInfo(obj, "kobe", 30, 1.98, "洛杉矶市")

  </script>

</body>
</html>

 ES5-Object是其他类的父类

也就是说只要是对象类型的,就能调用Object里面的函数。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script src="./js/inherit_utils.js"></script>
  <script>

    function Person() {}
    function Student() {}
    function Teacher() {}

    inherit(Student, Person)
    console.log(Person.prototype.__proto__ === Object.prototype)

    // 在Object的原型上添加属性
    Object.prototype.message = "coderwhy"
    var stu = new Student()
    console.log(stu.message)

    // Object原型上本来就已经存放一些方法
    console.log(Object.prototype)
    console.log(stu.toString())

    // 函数对象也是最终继承自Object
    function foo() {}
    console.log(foo.message)

  </script>

</body>
</html>

ES5-对象判断方法补充

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script src="./js/inherit_utils.js"></script>
  <script>

    var obj = {
      name: "why",
      age: 18
    }

    var info = createObject(obj)
    info.address = "中国"
    info.intro = "中国大好河山"

    console.log(info.name, info.address)
    console.log(info)

    // 1.hasOwnProperty
    // console.log(info.hasOwnProperty("name")) // false
    // console.log(info.hasOwnProperty("address")) // true

    // 2.in操作符
    console.log("name" in info)
    console.log("address" in info)
    // 注意: for in遍历不仅仅是自己对象上的内容, 也包括原型对象上的内容
    for (var key in info) {
      console.log(key)
    }

    // 3.instanceof
    // instanceof用于判断对象和类(构造函数)之间的关系
    function Person() {}
    function Student() {}
    inherit(Student, Person)

    // stu实例(instance)对象
    var stu = new Student()
    console.log(stu instanceof Student)
    console.log(stu instanceof Person)
    console.log(stu instanceof Object)
    console.log(stu instanceof Array)

    // 4.isPrototypeOf
    console.log(Student.prototype.isPrototypeOf(stu))
    console.log(Person.prototype.isPrototypeOf(stu))
    
    // 可以用于判断对象之间的继承
    console.log(obj.isPrototypeOf(info))

  </script>

</body>
</html>

ES5-构造函数的类方法 

添加到原型上面的方法叫实例方法,添加到类上面的叫类方法。

没有创建实例的情况下,不能使用实例方法,但是可以使用类方法。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>

    // var obj = {
    //   friend: {
    //     play: function() {
    //       console.log("play~")
    //     }
    //   }
    // }

    // obj.play()

    function Person(name, age) {
      this.name = name
      this.age = age
    }

    Person.totalCounter = "70亿"

    // 添加Person原型上的方法也成为 实例方法
    Person.prototype.running = function() {
      console.log(this.name + "running~")
    }
    Person.prototype.eating = function() {
      console.log("eating~")
    }

    // 添加Person对象本身的方法成为 类方法
    var names = ["abc", "cba", "nba", "mba"]
    Person.randomPerson = function() {
      var randomName = names[Math.floor(Math.random() * names.length)]
      return new Person(randomName, Math.floor(Math.random() * 100))
    }

    // 实例对象
    var p1 = new Person("why", 18)
    var p2 = new Person("kobe", 30)
    p1.running()

    // 没有实例对象的情况下, 能不能调用函数? 不可以调用
    var p = Person.randomPerson()
    console.log(p)

  </script>

</body>
</html>

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值