原型和继承

原型和继承

一、原型

说到原型之前,先复习一下对象创建方式:

1. 使用对象字面量的形式创建 (最简单)

<script>

  var obj = { a: 1}
  obj.b = 2
  console.log(obj)

</script>

2. 使用工厂函数的方式

<script>

  function CreatePerson (name, age) {
    var obj = new Object ()
    obj.name = name
    obj.age = age
    obj.sayHi = function () {
      console.log('哈喽')
    }
    return obj
  }
  var person1 = CreatePerson('张三', 18)
  var person2 = CreatePerson('李四', 20)

</script>

3. 使用构造函数的方式(对工厂函数的改进)

<script>

  function Person (name, age) {
    this.name = name
    this.age = age
    this.sayHi = function () {
      console.log('哈喽')
    }
  }
  var person1 = new Person('张三',18)
  var person2 = new Person('李四',20)

</script>
  • 用new操作符调用函数时,会自动执行下面的操作
  • 创建(或构造)一个新的对象
  • 这个新对象会执行prototype连接
  • 这个新对象会绑定到函数调用的this
  • 如果函数没有返回其他对象, 那么new表达式中的函数调用自动返回这个新对象
//我们来举个例子
var Foo() {
  //这是一个普通函数
}
Foo() //普通函数调用
var a = new Foo()//在普通调用前加一个new关键字后,这个调用就变成了构造函数调用
  • new会劫持所有普通函数,并用构造函数的形式调用它

var a = new Foo() 这句话的执行机制: Foo是一个普通函数,在使用new调用时,它就会构造一个对象,并且赋值给a

  • 简单点来说 js中的 构造函数 :所有带new的函数调用
  • 用new的方法来调用Foo会产生一个新对象a,这个新对象的内部链接__proto__会关联到Foo的原型对象上
    a.proto == Foo.prototype

4. 使用原型的方式

  • 原型的初步模式
<script>
  function Person () { }

  Person.prototype.name = '张三'
  Person.prototype.age = 18
  Person.prototype.sayHi = function () {
      console.log('哈喽')
    }

  var person1 = new Person()
  var person2 = new Person()
</script>

  • 优点:原型中所有的属性和方法都可以被实例对象共享
  • 缺点:所有的实例对象都公用一套原型和方法,没有差异化,不能修改
我们来了解一下实例对象、构造函数和原型之间的联系

我们来了解一下实例对象、构造函数和原型之间的联系

  • 优化模式(原型重构)
    上面例子,每次添加一个属性和方法,就要重新写一遍,比较繁琐,现在把这些属性和方法都放入一个对象中,再赋值给Person的原型
<<script>

  function Person () { }

  Person.prototype = {
    name: '张三',
    age: 18,
    sayHi: unction () {
      console.log('哈喽')
    }
  }

  var person1 = new Person()
  var person2 = new Person()

  console.log(person1.constructor == Person) // false
  console.log(person1.constructor == Object) // true

</script>

  • js 中所有的对象都有一个特殊的内置属性[[prototype]] ,这个属性其实就是用来对其他对象的引用
  • 这种写方法改变了原型内部的constructor指针的属性了 ,不再指向Person函数了,如果这个指针很重要,那么可以手动设置
<<script>

  function Person () { }

  Person.prototype = {
    constructor:Person,
    name: '张三',
    age: 18,
    sayHi: unction () {
      console.log('哈喽')
    }
  }

  var person1 = new Person()
  var person2 = new Person()

  console.log(person1.constructor == Person) // true
  console.log(person1.constructor == Object) //false

</script>

如果要访问对象中的属性,我们要经过哪些机制呢?
<script>

  var obj = {
    a: 2
  }

  console.log(obj.a) // 2

</script>


总结:prototype 的机制

prototype机制就是存在于对象中的一个内部链接,它会引用其他对象。

这个链接的作用就是: 如果在对象上没有找到需要的属性或者方法,引擎就会在prototype关联的对象上进行查找。如果在后者的对象中没有找到这个属性和方法,引擎就会继续查找他的prototype,以此内推,如果没有找到,就会返回underfind。
这就是所谓的原型链。(有点像嵌套的作用域链)


二、继承

构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象。

继承的方式:

1. 通过原型的方式继承

<script>
  function Bird(legNum, wingNum) {
    this.leg = legNum;
    this.wing = wingNum;
  }
  Bird.prototype.appearance = function () {
    console.log('鸟类都有漂亮的羽毛')
  }
  function Swan (beh,legNum,wingNum) {
    this.behaviour =  beh;
  }
  Swan.prototype = new Bird(2,2,);// 继承了Bird中的属性和方法,但是不能通过传参修改这些值

  Swan.prototype.story = function () {
    console.log('是由丑小鸭变成的');
  }
   // 白天鹅
  var whiteSwan = new Swan('会游泳')
  console.log(whiteSwan)
  whiteSwan.appearance() // 鸟类都有漂亮的羽毛
  whiteSwan.story() // 是由丑小鸭变成的
</script>
  • 优点:所有的属性和方法均可以进行继承
  • 缺点:如果继承了属性,属性值在后期无法修改

2. 使用call()方法调用实现继承

<script>
  function Person (name, age) {
  this.name = name;
  this.age = age;
  }
  Person.prototype.behaviour = function () {
    console.log('大家好,我是一类人')
  }


  function Man(name, age, gender) {
    Person.call(this, name ,age);
    this.gender = gender;
  }
  //可以创建多个实例对象
  var man1 = new Man('张三', 18, '男')
  var man2 = new Man('李四', 20, '女')
  //但是 不能调用原型中的属性和方法
  console.log(man1)
  console.log(man2)
  man1.behaviour()//会报错,这种方法继承不到Person函数中原型中的属性方法
</script>

call(this, 参数1 , 参数2...)

  • 使用call方法调用中的参数this 把this指向了调用者
  • 优点:可以继承所有在构造函数体中书写的属性,并且可以进行传参
  • 缺点: 无法继承原型中的属性和方法

3. 组合方式继承(使用call和原型的方式组合继承)

  • call方法:继承构造函数体中的属性,并可以传参修改
  • 原型方式:可以继承原型链中的属性
  • 看实例
<script>
 // 组合继承
 function Person (name, age) {
    this.name = name;
    this.age = age;
  }
  //Person的原型
  Person.prototype.behaviour = function () {
    console.log('大家好,我是一类人')
  }

  //console.log(Person())//underfined

  //Man 的构造函数
  function Man(name, age, gender) {
    //call调用的方法进行继承,这个方法改变了Man这个构造函数内部的指向(即constructor指针)
    Person.call(this, name ,age);//第二次调用Person
    this.gender = gender;
  }
  //这里的通过new创建了一个实例对象,并把这个实例对象赋值给了Man的原型对象, 所以改变了 Man原型对象的内部指针(constrouctor)
  //原本Man 的原型对象中的constructor指向Man这个函数的,但是现在被改变成指向Person这个函数了

  Man.prototype =  new Person() //第一次调用Person
  //Man 原型对象中的方法
  Man.prototype.action = function () {
    console.log('吃饭、睡觉、打豆豆')
  }
  //可以创建多个实例对象
  var man1 = new Man('张三', 18, '男')
  var man2 = new Man('李四', 20, '女')
  console.log(man1)
  man1.behaviour()
  man1.action()
</script>


  • 优点:所有的属性和方法均可以被继承,并且所有的属性均可自由传参。
  • 缺点:每调用一次Man构造函数时,就会调用两次Person函数

4. 拷贝继承方法 (将原对象进行遍历,把它的属性和方法赋值给新的对象)

  • 看代码
<script>
//构造函数
  function Person (name, age) {
    this.name = name;
    this.age = age;
  }
  Person.prototype.behaviour = function () {
    console.log('大家好,我是一类人')
  }

  function Man (name, age, gender) {
    this.gender = gender
    var person = new Person(name,age)
    // 在Man函数内部对Person进行遍历, 把People具有的属性赋值给Man
    for (var k in person) {
      this[k] = person[k]
    }
  }
  Man.prototype.action = function () {
    console.log('吃饭、睡觉、打豆豆')
  }
  //创建实例对象man
  var man = new Man('张三', 18, '男')
  var man2 = new Man('李四', 20, '女')
  console.log(man.name)
  console.log(man.age)
  console.log(man)
  console.log(man2)
  man.behaviour()

</script>
  • 优点:可以传参,可以继承到原型中属性和方法

    5. this的方式(这种方式与call的方式类似)

<<script>
   //构造函数
  function Person (name, age) {
    this.name = name
    this.age = age
  }
  Person.prototype.behaviour = function () {
    console.log('大家好,我是一类人')
  }

  function Man (name, age, gender) {
  //在Man中添加一个属性people,把People这个函数体赋值给Man的属性people,然后再进行调用
    this.people = Person
    //进行调用
    this.people(name, age)

    this.gender = gender
  }
  Man.prototype = function () {
    console.log('吃饭、睡觉、打豆豆')
  }
  //实例对象
  var man = new Man('张三', 18 , '男')

  console.log(man)
  man.behaviour()// 报错  不能继承到Person原型中的属性方法
  </script>

  • 优点: 可以传参
  • 缺点:原型内的方法无法继承,并且还多了一个属性people
原文地址:https://www.cnblogs.com/queen-live/p/7846415.html