一. JavaScript原型

  1. 每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针

名词解释:指针后台语言说话,在前端就是属性的意思

  1. <script>
  2. // 1. 每个构造函数都有一个prototype属性,指向了它的原型对象(打开控制台查看试试)
  3. // 2. 原型对象都存在一个属性constructor指向了构造函数,例如Date构造函数:
  4. console.log(Array.prototype);
  5. </script>
  1. 每个实例都包含一个指向原型 对象的内部指针
    名词解释: 实例就是构造函数new出来的对象

    1. <script>
    2. // a.每个实例都存在一个属性__proto__, 指向了原型对象
    3. var date = new Date();
    4. var arr = new Array(1, 2, 3);
    5. console.log(date.__proto__);
    6. console.log(arr.__proto__);
    7. // 根据第1点每个构造函数都有一个原型对象, 思考问题: 构造器的prototype和实例的__proto是什么关系
    8. console.log(Array.prototype === arr.__proto__);
    9. </script>
    10. 实例继承原型对象的所有方
  2. 实例继承原型对象的所有方法

    1. <script>
    2. // 1. 创建数组对象
    3. var arr = [1, 2, 3, 4];
    4. // 2. arr就会拥有length属性,push,pop等方法,请问它的属性和方法是它的原型对象给的
    5. console.log(arr.length);
    6. arr.push(5);
    7. console.log(arr);
    8. // 3. 自定义一个构造函数, 给它的构造器和原型对象添加属性或方法
    9. function MadeCat() {
    10. this.name = '猫';
    11. }
    12. MadeCat.prototype.color = '黑色';
    13. MadeCat.prototype.sayName = function() {
    14. console.log(this.name);
    15. }
    16. var cat = new MadeCat();
    17. console.log('cat', cat);
    18. </script>
  3. 构造函数, 原型对象,实例三者关系的比喻(看图)

实例-构造函数-原型对象关系图(单个).png

  1. 实例、构造函数、原型对象三者的关系就好像儿子、妈妈、爸爸的关系
  2. 1.每一个妻子(构造函数)都有一个丈夫(原型对象),丈夫(原型对象)身上有一个结婚证,结婚证上那个女人的名字就是他妻子(构造函数)
  3. 2.小孩是由妈妈生产出来的(就好像实例是由构造对象new出来一样),每个小孩(实例)都有一个爸爸(原型对象)
  4. 结论:
  5. 1.构造函数.prototype === 原型对象
  6. 2.实例.__proto__ === 原型对象
  7. 3.构造函数.prototype === 实例.__proto__; // 原型对象是同一个

二. JavaScript原型链

  1. 原型和实例的关系:
    实例内容有一个指针指向了原型对象(换一种说法就是实例有一个属性指向了原型对象)
    1. // 创建日期实例(对象)
    2. var date = new Date();
    3. // 日期实例有一个属性__proto__指向了原型对象
    4. console.log('原型对象=',date.__proto__);
    2. 原型链:
    实例、构造函数、原型对象关系图
    实例-构造函数-原型对象关系图(多个).png结论: 根据上图, 存在以下关系: ```javascript var arr = [1,2,3]; var date = new Date(); var obj = {name:’zs’};

// 1.实例.proto===构造函数.prototype date.proto === Date.prototype; arr.proto === Array.prototype; cat.proto === MadeCat.prototype; obj.proto === Object.prototype;

// 2.终极原型都一样, 都是Object的原型对象 date.proto.proto === Object.prototype; arr.proto.proto === Object.prototype; cat.proto.proto === Object.prototype;

// 3. 根据第2点得到 date.proto.proto === arr.proto.proto; arr.proto.proto === cat.proto.proto; date.proto.proto === cat.proto.proto;

  1. 2. 原型链概念:假如我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型对象的指针,假如另一个原型对象又是另一个类型的实例,name上述关系依然成立,如此层层递进,就构成了实例与原型的链条.这就是所谓原型链的基本概念
  2. ```javascript
  3. //举例说明
  4. //动物类型构造器
  5. function MadeAnimal() {
  6. this.type = '动物'
  7. }
  8. //创建一个动物的实例
  9. var animal = new MadeAnimal();
  10. //猫类型构造器
  11. function MadeCat(name,age){
  12. this.name = name;
  13. this.age = age;
  14. }
  15. //让猫的原型对象等于动物类型的一个实例
  16. MadeCat.prototype = animal;
  17. var cat = new MadeCat('小花猫',2);
  18. //就会纯在以下的关系:
  19. 1.猫的实例cat存在一个属性__proto__指向了猫的原型对象,
  20. 2.因为猫的原型对象是动物的一个实例animl,既然它是个实例,则animl实例就会存在一个__proto__属性,指向了动物的原型对象
  21. 3. 实际上,动物的原型对象还纯在一个__proto__属性,它指向了Objert的原型对象,所有以下的等式是成立的
  22. cat.__proto__ 猫的原型对象是animl
  23. cat.__proto__.__proto__ 这是animl的原型对象
  24. cat.__proto__.__proto__.__proto__ animal原型对象内部还存在一个__proto__属性,指向了Object.prototype
  25. car.__proto__.__proto__.__proto__ === Object.prototype; //

问到原型和原型链就这么回答:
原型: 原型值得是原型对象,每一个构造函数都会有一个对象的原型对象,同时原型对象也存在一个指针(属性),指向了它的构造器.
原型链:

  1. 我们创建的每一个实例,都存在一个内部指针(属性,叫proto),指向了它的原型对象,并且会继承原型对象的属性和方法
  2. 假如我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,假如另一个原型对象又是另一个类型的实例,name上述关系依然成立,如此层层递进,就构成了实例与原型的链条.这就是原型链的基本概念.

JavaScript继承

需要掌握call,apply和bind的知识
JavaScript5.1版常见的有5种继承方式:原型继承,借用构造函数继承,寄生式继承,组合继承,寄生组合式继承

1. 原型继承: 让儿子类的原型对象=父类的实例

缺点:
父类的引用类型属性会被所有子类实例共享,任何一个子类实例修改了父类的引用类型属性,其他子类实例都会收到影响
创建子类实例的时候,不能向父类传参

  1. <script>
  2. // 1. 父类
  3. function Father() {
  4. this.type = 'father';
  5. this.names = ['tom', 'kevin'];
  6. }
  7. // 2.给父类Father的原型对象添加getName方法
  8. Father.prototype.getName = function() {
  9. console.log(this.names);
  10. }
  11. // 3.创建父类的实例
  12. var father = new Father();
  13. // console.log(father);
  14. // 2. 子类
  15. function Child() {
  16. }
  17. // 3. 让子类的原型对象等于父类的一个实例, 完成原型链继承
  18. Child.prototype = father;
  19. // 4. 创建一个子类实例
  20. var child = new Child();
  21. console.log(child);
  22. // 5. 创建另外一个实例
  23. var child2 = new Child();
  24. console.log(child2);
  25. // 6. 缺点展示: 给其中一个实例的names添加一个成员
  26. child2.names.push('zs');
  27. console.log('child2.names', child2.names);
  28. console.log('child.names', child.names);
  29. // 不是引用类型,不受影响
  30. child2.type = 'aaaa';
  31. console.log('child2.type', child2.type);
  32. console.log('child.type', child.type);
  33. </script>

2. 借用构造函数继承

在子类的构造函数中调用父类的构造器实现继承
优点:
避免了引用类型属性被所有实例共享
可以向父类传参
缺点:
方法必须定义在构造函数中,定义在父类原型对象上的方法无法继承
每创建一个实例都会创建一个方法

  1. <script>
  2. // 1. 父类构造器
  3. function Father(name) {
  4. this.name = name;
  5. this.list = [1, 2, 3];
  6. this.say = function() {
  7. console.log('hello');
  8. }
  9. }
  10. // Child的实例无法继承Father原型上的属性或方法
  11. Father.prototype.aaa = function() {
  12. console.log('aaa');
  13. }
  14. // 2. 子类
  15. function Child(name) {
  16. // 3. 借用父类的构造器
  17. Father.call(this, name);
  18. }
  19. // 4. 创建实例1
  20. var child = new Child('小王');
  21. // 5. 创建实例2
  22. var child2 = new Child('小红');
  23. child2.list.push(4);
  24. console.log('child2', child2.list);
  25. console.log('child', child.list);
  26. // 6. 缺点展示: 方法会创建很多遍
  27. console.log(child.aaa); // undefined,方法只能定义在父类的构造函数里
  28. console.log(child.say === child2.say); // false, 所以每个实例都会创建一遍方法
  29. </script>

3. 寄生式继承

需要先传入一个对象,在网对象上添加属性或方法,就好像寄生一样
就是创建一个封装的函数来增强原有对象的属性,缺点跟借用构造函数一样,每个实例都会创建一遍方法

  1. <script>
  2. //1.声明对象
  3. var person = {
  4. type:'人类'
  5. say: funciton(){
  6. console.lo(this.type)
  7. }
  8. }
  9. //2. 使用Object.create创建的新对象
  10. var obj = Object.create(person);
  11. console.log('obj',obj);//存在这样的关系:obj__proto__ === person
  12. </script>
  13. <script>
  14. //1. 声明一个函数
  15. function madeObj(o){
  16. //2. 根据传入的对象使用Object.create创建一个对象,新的对象继承了传入对象的一切属性和方法
  17. var obj = Object.create(o); //obj__proto__ === 0
  18. //3. 给新的对象添加sayName
  19. obj.sayName = function(){
  20. console.log(this.name)
  21. }
  22. //返回新的对象
  23. return obj;
  24. }
  25. //person相当于原型对象的角色
  26. var person = {
  27. name:'人',
  28. nation:'中国',
  29. sayHello: function() {
  30. console.log('hello');
  31. }
  32. }
  33. //5. 调用madeObj方法创建对象
  34. var p = madeObj(person);
  35. console.log(p) //p对象继承了person对象的属性和方法
  36. p.sayName();
  37. </script>

4.组合式继承(第一种和第二组组合)
既有原型继承,也借用了父类的构造函数
组合式继承方式唯一的缺点是,父类的构造函数会调用两次

  1. <script>
  2. // 1.创建父类
  3. function Father(name, age) {
  4. this.name = name;
  5. this.age = age;
  6. this.list = [1, 2, 3];
  7. }
  8. // 2.给父类的原型对象添加say方法
  9. Father.prototype.say = function() {
  10. console.log('hello');
  11. }
  12. // 3.创建子类
  13. function Child(name, age) {
  14. // 4.借用父类构造函数
  15. Father.call(this, name, age);
  16. }
  17. // 5.让子类的原型对象等于父类的一个实例,这样子类就继承了父类的所有属性和方法,new Father时可以传参也可以不传
  18. Child.prototype = new Father('张无忌', 50);
  19. // 6.创建子类实例
  20. var child = new Child('张三', 18);
  21. console.log('child', child);
  22. var child2 = new Child('李四', 20);
  23. console.log('child', child2);
  24. console.log(child.list === child2.list); //false,引用类型属性不会相互影响
  25. </script>

5. 寄生组合式继承(最完美的继承方式)

完美的继承方式应该是这样:
a. 属性定义在父类构造函数里,并且这些属性被子类继承的时候不会相互影响
b. 公共方法定义在父类的原型对象里,子类可以共享这些方法

  1. <script>
  2. // 1. 创建一个组合式继承
  3. function Father(name, age) {
  4. this.name = name;
  5. this.age = age;
  6. this.list = [1, 2, 3];
  7. }
  8. Father.prototype.say = function() {
  9. console.log(this.name);
  10. }
  11. function Child(name, age) {
  12. // 借用构造函数
  13. Father.call(this, name, age);
  14. }
  15. // 2. 添加寄生式继承Object.create
  16. Child.prototype = Object.create(Father.prototype);
  17. // Child.prototype.__proto__ === Father.prototype; 产生原型的继承关系
  18. console.log(Child.prototype.constructor === Child); // false, 因为寄生式继承破坏了原来的原型关系
  19. // 3. 重新制定子类原型对象的构造函数
  20. Child.prototype.constructor = Child;
  21. var child = new Child('张三', 18);
  22. console.log('child', child);
  23. </script>

四. JavaScript继承总结

  1. 原型链继承实现步骤

    1. 创建一个父类(准确来说是父类构造函数)
    2. 创建一个子类
    3. 让子类的prototype = 父类的一个实例
      1. 定义:让子类的prototype等于父类的一个实例
      2. 缺点:
      3. 父类的引用类型属性会被所有子类实例共享,任何一个子类实例修改了父类的引用类型属性,其他子类实例都会受到影响
      4. 创建子类实例的时候,不能向父类传参
  2. 借用构造函数继承实现步骤

    1. 创建一个父类
    2. 创建一个子类
    3. 在子类的构造函数里使用call或者apply调用父类的构造函数
      1. 定义:在子类的构造函数中调用父类的构造函数实现继承
      2. 有点:
      3. 避免了引用类型属性被实例共享
      4. 可以向父类传参
      5. 缺点:
      6. 方法必须定义在构造函数中,定义在父类原型对象的方法子类无法继承
      7. 每创建一个实例都会创建一遍方法,也就是方法不能共享
  3. 寄生式继承实现步骤

    1. 创建一个用于继承的对象(可以称为父类对象)
    2. 创建一个构造方法
    3. 在构造方法里接收父类对象作为参数,并调用Object.crate,传入父类对象来创建一个新的对象
    4. 给新的对象创建所需要的方法
    5. 返回新对象,就能得到一个继承了父类对象所有属性和方法的对象
      1. 定义: 传入一个对象作为寄生的对象,根据传入对象创建一个新对象,再往对象上添加属性或方法,就好像寄生一样
      2. 缺点:跟借用构造函数一样,每个实例都会创建一遍方法,也就是方法不能共享
  4. 组合式继承(第一种和第二组组合) 实现步骤

    1. 创建父类,给父类的原型对象添加公共方法
    2. 创建子类
    3. 在子类中调用父类的构造函数,传入所需要的参数(如果需要的话)
    4. 让子类的原型对象等于父类的一个实例(new一个父类实例)
      1. 定义:既有原型继承,也借用了父类的构造函数,实现继承使得子类可以共享父类的方法,方法不需要创建多次
      2. 借用构造函数使得可以调用继承父类的属性
      3. 组合式继承方式唯一的缺点是,父类的构造函数会被调用两次
  5. 寄生组合式继承(最完美的继承方式)

    1. 完美的继承方式应该具备以下特点
    2. 子类实例可以继承父类的属性(包含基本数据类型和引用数据类型的属性)
    3. 同时引用数据类型的属性不会相互影响
    4. 可以共享父类原型对象的方法,这些方法只需要创建一遍

组合寄生式继承符合了以上条件,它的实现步骤:
创建一个具备组合式继承父类和子类
使用Objert.create方法,传入父类原型对象,得到新对象赋给子类的原型对象
重新制定子类的原型对象的构造函数为子类构造函数

  1. 组合式继承已经满足了以下条件,唯一的缺点接收父类构造函数会调用2
  2. 寄生式继承,使用Object,create让子类原型对象跟父类原型对象产生了关系,从而实现的继承,它不需要new一个父类的实例,所以父类的构造函数器不需要调用两次