js高级基础语法:面向对象 + 原型 + 继承

第一章:JavaScript 是一个编程语言

  • 解析执行:解释一行执行一行。执行速度慢

    编译执行:一次性把代码编译成可执行的代码,然后再一行一行的执行。执行速度较快

  • 语言特点:动态,头等函数 (First-class Function)

    • 又称函数是 JavaScript 中的一等公民
  • 执行环境:在宿主环境(host environment)下运行,浏览器是最常见的 JavaScript 宿主环境

    • 但是在很多非浏览器环境中也使用 JavaScript ,例如 node.js

    MDN-JavaScript

JavaScript 的组成

  • ECMAScript - 语法规范
    • 变量、数据类型、类型转换、操作符
    • 流程控制语句:判断、循环语句
    • 数组、函数、作用域、预解析
    • 对象、属性、方法、简单类型和复杂类型的区别
    • 内置对象:Math、Date、Array,基本包装类型String、Number、Boolean
  • Web APIs
    • BOM
      • onload页面加载事件,window顶级对象
      • 定时器
      • location、history
    • DOM
      • 获取页面元素,注册事件
      • 属性操作,样式操作
      • 节点属性,节点层级
      • 动态创建元素
      • 事件:注册事件的方式、事件的三个阶段、事件对象

JavaScript 可以做什么

浏览器是如何工作的

先找域名对应的ip地址,找到ip地址后

浏览器向ip地址对应的服务器发送请求,服务器将请求的内容返回给浏览器(一般是html的网页)

而通过网络接收到的就是一些字符串,这些字符串需要解析器执行里面的代码。需要了解浏览器的组成部分

  • User Interface 用户界面,我们所看到的浏览器

  • Browser engine 浏览器引擎,(需要和渲染引擎进行交互)用来查询和操作渲染引擎

  • Rendering engine 渲染引擎,负责渲染HTML、CSS,并且生成DOM树`(DOM树存储在内存中)

    • Networking 网络,负责发送网络请求,接收服务器返回的内容。

      1. 接收的内容就是一些字符串,里面包含:html/css/js,然后将这些字符串交给渲染引擎,

      2. 渲染引擎会解析html和css,生成DOM树。然后再来执行文档里面的js代码

      3. 在执行js代码的时候,我们有时会写一些对话框。这些对话框由UI Backend进行处理。

        它是负责和操作系统进行交互。

      4. 我们有时可能要存储一些数据,这些数据交给Data Persistence 数据持久层进行处理。

    • JavaScript Interpreter JavaScript解析器,负责执行JavaScript的代码。处理DOM和BOM

      如果是DOM操作的话,会更新内存中的DOM树,然后渲染引擎会重新渲染DOM树

      接着通知浏览器引擎,将更新的DOM树渲染到页面

    • UI Backend UI后端:负责和操作系统进行交互,用来绘制类似组合框和弹出窗口

  • Data Persistence(持久化) 数据持久化,数据存储 cookie、HTML5中的sessionStorage

在这里插入图片描述

参考链接

JavaScript 执行过程

JavaScript 运行分为两个阶段:

  • 第一步:预解析

    • 执行全局预解析(所有变量和函数声明都会提前;同名的函数和变量函数的优先级高)

    • 执行函数内部预解析(所有的变量函数形参都会参与预解析)

      找到:函数形参普通变量

  • 第二步:执行代码

先预解析全局作用域,然后执行全局作用域中的代码,

在执行全局代码的过程中遇到函数调用就会先进行函数预解析,然后再执行函数内代码。


第二章:JavaScript 面向对象编程

面向对象介绍

什么是对象

对象到底是什么,我们可以从两次层次来理解。

(1) 现实中的对象:对象是单个事物的抽象。

  • 实体:一本书、一辆汽车、一个人都可以是对象
  • 虚体:一个数据库、一张网页、一个与远程服务器的连接也可以是对象。

当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程

(2) 程序中的对象:对象是一个容器,封装了属性(property)和方法(method)。

属性是对象的状态,方法是对象的行为(完成某种任务)。比如:

  • 我们可以把动物抽象为animal对象
    • 使用“属性”记录具体是那一种动物
    • 使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。

温馨提示

  • 每个对象都是基于一个引用类型创建的,这些类型可以是系统内置的原生类型,如:数组对象
  • 也可以是开发人员自定义的类型:构造函数创建对象

什么是面向对象

面向对象不是新的东西,它只是过程式代码的一种高度封装,目的在于提高代码的开发效率和可维护性

面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。

它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。

在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。
因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发

面向对象与面向过程的区别:
  • 面向过程就是亲力亲为,事无巨细,面面俱到,步步紧跟,有条不紊
  • 面向对象就是找一个对象,指挥得结果
  • 面向对象将执行者转变成指挥者
  • 面向对象不是面向过程的替代,而是面向过程的封装
面向对象的特性:
  • 封装性
  • 继承性
  • [多态性]抽象

扩展阅读:

面向对象开发的好处:
  • 多人协同开发
  • 方便代码维护和更新。(哪里有错,就找哪个对象)
程序中面向对象的基本体现

如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,
而是 Student 这种数据类型应该被视为一个对象,这个对象拥有 namescore 这两个属性(Property)。
如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个 printScore 消息,让对象自己把自己的数据打印出来。

<script>
    // 打印学生的成绩表
    
    // 1 面向过程的方式
    // 1.1 记录学生的成绩
    var stu1 = {name: 'zs', subject: '语文', score: 90};
    var stu2 = {name: 'ls', subject: '语文', score: 80};
    // 1.2 打印学生的成绩
    console.log(stu1.name, stu1.subject, stu1.score);
    console.log(stu2.name, stu2.subject, stu2.score);


    //2 面向对象的方式
    // 创建一个模板,用于创建对象(实例instance)
    // 在JavaScript中创建对象的模板是构造函数
    // 而在其他语言中创建对象的模板是类
    function Student(name, subject, score) {
      /*
        当使用new一个构造函数的时候,会产生一个对象,并且this就会指向这个对象
        给this增加一些属性就是给新创建的对象增加属性
      */
      this.name = name;
      this.subject = subject;
      this.score = score;

      this.printScore = function () {
        console.log(this.name, this.subject, this.score);
      }
    } 

    var stu1 = new Student('zs', '语文', 90);
    var stu2 = new Student('ls', '语文', 80);

    stu1.printScore();
    stu2.printScore();
  </script>

创建对象的方式

  <script>
    // ************************************创建对象的方式有哪些?*************************

    // 方法1: new Object()
    var hero = new Object();  // 空对象
    hero.blood = 100;
    hero.name = '刘备';
    hero.weapon = '剑';

    hero.attack = function () {
      console.log(this.weapon + ' 攻击敌人');
    }

    // 方法2: 对象字面量   如:var hero = {};  空对象

    var hero = {
      blood: 100,
      name: '刘备',
      weapon: '剑',
      attack: function () {
        console.log(this.weapon + ' 攻击敌人');
      }
    }

    hero.attack();
    
    // 方法3: 工厂函数 创建多个对象  缺点:无法判断对象具体的数据类型
    function createHero(name, blood, weapon) {
      var o = new Object();
      o.name = name;
      o.blood = blood;
      o.weapon = weapon;
      o.attack = function () {
        console.log(this.weapon + ' 攻击敌人');
      }
      return o;
    }

    var  hero = createHero('刘备', 100, '剑');
    var  hero1 = createHero('关羽', 100, '刀');

  </script>

通过工厂模式我们解决了创建多个相似对象代码冗余的问题,
但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

构造函数

构造函数如何工作的呢?

当调用new Hero()的时候:

  1. 会在内存中创建一个空对象
  2. 设置构造函数的this,让this指向刚刚创建好的对象
  3. 执行构造函数中的代码
  4. 返回对象

下面是具体的伪代码:

// 构造函数 -- 构造对象的函数:首字母大写
    function Hero(name, blood, weapon) {
      this.name = name;
      this.blood = blood;
      this.weapon = weapon;

      this.attack = function () {
        console.log(this.weapon + ' 攻击敌人');
      }
    } 
	
    var hero = new Hero('刘备', 100, '剑');
    hero.attack();

 	// 第一点:无法使用typeof获取对象的具体类型
    console.log(typeof hero);
    var arr = new Array();
    console.log(typeof arr);

    // 第二点:使用 constructor 构造器  获取对象的具体类型 (不建议使用)
    console.log(hero.constructor === Hero);
    var arr = [];  //new Array()
    console.log(arr.constructor === Array);

    // 第三点:使用 instanceof 判断某个对象是否是某个构造函数的实例/对象
    console.log(hero instanceof Hero);
    var arr = [];
    console.log(arr instanceof Array);
静态成员和实例成员
 <script>
        function Hero(name, blood, weapon) {

            //(位于构造函数内部)实例成员 / 对象成员 -- 跟对象相关的成员,将来使用对象的方式来调用
            this.name = name;
            this.blood = blood;
            this.weapon = weapon;

            this.attack = function() {
                console.log(this.weapon + ' 攻击敌人');
            }
        }

        // 静态成员(与对象无关) -- 直接给构造函数添加的成员
        Hero.version = '1.0';

        var hero = new Hero('刘备', 100, '剑');
        hero.attack(); // 剑 攻击敌人

        var hero1 = new Hero('关羽', 100, '刀');
        hero1.attack(); // 刀 攻击敌人

        // 静态成员不能使用对象的方式来调用
        console.log(hero.version); // undefined

        // 静态成员使用构造函数来调用
        console.log(Hero.version); // 1.0
    </script>
构造函数和实例对象的关系

使用构造函数的好处不仅仅在于代码的简洁性,更重要的是我们可以识别对象的具体类型了。
在每一个实例对象中同时有一个 constructor 属性,该属性指向创建该实例的构造函数:

console.log(p1.constructor === Person) // => true
console.log(p2.constructor === Person) // => true
console.log(p1.constructor === p2.constructor) // => true

对象的 constructor 属性最初是用来标识对象类型的,
但是,如果要检测对象的类型,还是使用 instanceof 操作符更可靠一些:

console.log(p1 instanceof Person) // => true
console.log(p2 instanceof Person) // => true

总结:

  • 构造函数是根据具体的事物抽象出来的抽象模板
  • 实例对象是根据抽象的构造函数模板得到的具体实例对象
  • 每一个实例对象都具有一个 constructor 属性,指向创建该实例的构造函数
    • 注意: constructor 是实例的属性的说法不严谨,具体后面的原型会讲到
  • 可以通过实例的 constructor 属性判断实例和构造函数之间的关系
    • 注意:这种方式不严谨,推荐使用 instanceof 操作符,后面学原型会解释为什么
构造函数的问题

使用构造函数带来的最大的好处就是创建对象更方便了,但是其本身也存在一个浪费内存的问题:

// 构造函数
        function Student(name, age, sex) {
          this.name = name;
          this.age = age;
          this.sex = sex;

          // 多个对象,会存储多个sayHi方法
          this.sayHi = function () {
            console.log('大家好,我是' + this.name);
          }
        } 

        // 创建对象
        var s1 = new Student('lilei', 18, '男');
        var s2 = new Student('hmm', 18, '女');

        s1.sayHi();
        s2.sayHi();

		// 内存地址不同
        console.log(s1.sayHi === s2.sayHi); // false
解决方法:通过原型对象增加方法
<script>
        // 每一个构造函数都有一个属性prototype(原型),它也是一个对象,称为:原型对象

        function Student(name, age, sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }

        // console.dir(Student.prototype);  // object

        // 通过原型对象增加的方法:
        Student.prototype.sayHi = function() {
            console.log('大家好,我是' + this.name);
        }

        // 通过Student构造函数,创建的对象,可以访问Student.prototype中的成员
        var s1 = new Student('lilei', 18, '男');
        var s2 = new Student('hmm', 18, '女');

        s1.sayHi(); // 大家好,我是lilei
        s2.sayHi(); // 大家好,我是hmm

        console.log(s1.sayHi === s2.sayHi); //true
    </script>

第三章:原型

更好的解决方案: prototype

JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。
这个对象的所有属性和方法,都会被构造函数的所拥有。

这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。

 <script>
        // 每一个构造函数都有一个属性   原型 / 原型对象
        function Student(name, age, sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;

            // this.sayHi = function () {
            //   console.log('test');
            // }
        }
        Student.prototype.sayHi = function() {
            console.log('大家好,我是' + this.name);
        }

        // 通过Student构造函数,创建的对象,可以访问Student.prototype中的成员
        var s1 = new Student('lilei', 18, '男');
        var s2 = new Student('hmm', 18, '女');

        s1.sayHi();
        console.dir(s1);

        // 当调用对象的属性或者方法的时候,先去找对象本身的属性/方法 ,如果对象没有该属性或者方法。此时去			  调用原型中的属性/方法
        // 如果对象本身没有该属性/方法,原型中也没有该属性或者方法,此时会报错
        // s1.__proto__    对象的__proto__  等于  构造函数的Student.prototype
        // __proto__属性是非标准的属性(各个浏览器的属性名可能不一样)
        
        console.log(s1.__proto__ === Student.prototype); // true
        console.dir(s1.__proto__); //
        console.dir(Student.prototype);


        // 在原型对象中有一个属性 constructor(构造函数)
        
        // constructor作用:记录了创建该对象的构造函数  
        // 对象可以访问原型中的所以成员s1.constructor
        console.log(s1.constructor === Student); // true

        var arr = []; // 数组也是对象,而对象可以访问原型中的所以成员
        // Array.prototype.constructor
        console.log(arr.constructor === Array);
    </script>
构造函数、实例、原型三者之间的关系
<script>
    // 构造函数   原型对象   实例/对象s1.__proto__  之间的关系
    function Student(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    } 
    Student.prototype.sayHi = function () {
      console.log('大家好,我是' + this.name);
    }
    // 通过Student构造函数,创建的对象,可以访问Student.prototype中的成员
    var s1 = new Student('lilei', 18, '男');
    var s2 = new Student('hmm', 18, '女');

    // Student.prototype.constructor  指向了构造函数本身
  </script>

在这里插入图片描述

任何函数都具有一个 prototype 属性,该属性是一个对象。

function F () {}
console.log(F.prototype) // => object

F.prototype.sayHi = function () {
  console.log('hi!')
}

构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数。

console.log(F.prototype.constructor === F) // => true

通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__

var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true

`__proto__` 是非标准属性。

实例对象可以直接访问原型对象成员。

instance.sayHi() // => hi!
经验总结:
  • 任何函数都具有一个 prototype 属性,该属性是一个对象
  • 构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数
  • 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__
  • 所有实例都直接或间接继承了原型对象的成员
属性成员的搜索原则:原型链

在这里插入图片描述

总结:

  • 先在自己身上找,找到即返回
  • 自己身上找不到,则沿着原型链向上查找,找到即返回
  • 如果一直到原型链的末端还没有找到,则返回 undefined
属性查找的规则
  <script>

    // 属性查找规则
    function Student(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    }

    Student.prototype.sayHi = function () {
      console.log('大家好,我是' + this.name);
    }

    // 构造函数的原型对象增加一个属性
    Student.prototype.test = 'abc';
    var s1 = new Student('lilei', 18, '男');
    var s2 = new Student('hmm', 18, '女');


    // 证明:s1对象的原型对象的原型对象 等价于 Object构造函数的原型对象
    // console.log(s1.__proto__.__proto__ === Object.prototype);  //true


    // 证明:所有的对象都有toString()方法
    console.log(s1.toString());
    console.dir(s1);
    console.dir(s1.__proto__);
    console.dir(s1.__proto__.__proto__);


    // 1、如何读取属性  先在对象本身查找test属性,如果没有找到的话,会去原型链上查找
    // console.log(s1.name);
    // console.log(s1.test);

    // 2、如何设置属性
    s1.name = 'xxx';
    // test属性在原型对象上,而在设置属性的值的时候,不会搜索原型链而是直接给对象新增一个test属性
    s1.test = '123xxx';
    // 为s1对象新增一个address属性(只属于s1对象)
    s1.address = '杭州'

    console.log(s1.name);    // 打印结果:xxx
    console.log(s1.test);    // 打印结果:123xxx (s1本身有test属性,使用本身的test)
    console.log(s1.address); // 打印结果:杭州

    console.log(s2.name);    // 打印结果:hmm
    console.log(s2.test);    // 打印结果:abc    (s2本身没有test属性,使用原型对象的test)
    console.log(s2.address); // 打印结果:undefined(s2本身没有address属性且原型链上也没有,所以返回:undefined)
  </script>

使用原型对象的注意点
  • 当我们改变构造函数的prototype的时候,需要重新设置constructor属性

    原因如下:
    
    改变原型对象的prototype属性,可以设置为一个新的对象,这个新的对象没有constructor属性
    
    根据原型链查找,原型对象的原型对象有constructor属性,
    
    而 原型对象中的成员 constructor =》指向 创建对象所使用的构造函数,指向:系统默认object
    此时:对象.constructor属性 =》 指向:系统默认object (而不是我们想要的构造函数)
    
  • 先去设置原型属性,再创建对象,才可以访问原型对象中的成员

修改原型对象之前

Student.prototype构造函数的prototype属性,是一个对象,称为:原型对象

原型对象的constructor属性 =>指向 创建该对象的构造函数 Student

  <script>
    function Student(name, age) {
      
      // 一般情况下,对象的属性在构造函数中来设置
      this.name = name;
      this.age = age;
      
    }
    var stu = new Student('ls', 18);

	// 修改原型对象之前
    console.dir(Student.prototype); // 打印结果如下:
    // Object
    //   constructor: ƒ Student(name, age)
    //   __proto__: Object

  </script>

修改原型对象之后

面试题:为什么改变了构造函数的prototype属性之后,对象的constructor属性指向会发生改变?

因为:s1对象没有constructor属性,根据原型链查找,原型对象的原型对象有constructor属性,

而 原型对象中的成员 constructor =》指向 创建对象所使用的构造函数,指向:系统默认object

  <script>
    function Student(name, age) {
      
      // 一般情况下,对象的属性在构造函数中来设置
      this.name = name;
      this.age = age;
      
    }

 // 1. 对象的方法在构造函数的原型对象中来设置,可以重新改变原型对象的prototype属性,设置为一个新的对象。	   但是这么写会有一个问题:当我们改变构造函数的prototype的时候,需要重新设置constructor属性

    Student.prototype = {
      sayHi: function () {
        console.log('sayHi');
      },
      eat: function () {
        console.log('eat');
      }
    }
    
	// 修改原型对象之后且没有重新设置constructor属性,此时指向 object
    // 因为s1对象没有constructor属性,根据原型链查找,原型对象的原型对象有constructor属性,
    // 而 原型对象中的成员 constructor =》指向 创建对象所使用的构造函数,指向:系统默认object
    var s1 = new Student('zs', 18);
    console.log(s1.constructor); // 通过对象访问原型对象的成员(因为存在原型链)
	// 结果:ƒ Object() { [native code] }
  </script>

如果修改了prototype属性,就不能通过对象.constructor,找到我们想要的构造函数,如何解决呢?

解决方法:

我们为了保持 constructor 的指向正确,建议的写法是:

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

Person.prototype = {
  constructor: Person, // => 手动将 constructor 指向正确的构造函数
  type: 'human',
  sayHello: function () {
    console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
  }
}
 
var s1 = new Person('zs', 18);
console.log(s1.constructor); // 打印结果如下:此时 s1.constructor 指向 该对象的构造函数 Person
// ƒ Person(name, age) {
//     this.name = name
//     this.age = age
// }
原型对象使用建议
  • 私有成员(一般就是非函数成员)放到构造函数中
  • 共享成员(一般就是函数)放到原型对象中
  • 如果重置了 prototype 记得修正 constructor 的指向
原型对象的应用:扩展内置对象

注意:数组 或者String 中的prototype是不可以修改的,下面的写法语法上不允许,系统报错。

<script>
        var array = [5, 4, 1, 8];

        // 给数组的原型对象新增方法:
        Array.prototype.getSum = function() {
            // 求数组中所有偶数的和
            var sum = 0;
            for (var i = 0; i < this.length; i++) {
                if (this[i] % 2 === 0) {
                    sum += this[i];
                }
            }
            return sum;
        }
        console.log(array.getSum());

        // 注意:数组 或者String 中的prototype是不可以修改的,下面的写法语法上不允许,系统报错。
        // Array.prototype = {
        //   getSum: function () {
        //      // 求数组中所有偶数的和
        //     var sum = 0;
        //     for (var i = 0; i < this.length; i++) {
        //       if (this[i] % 2 === 0) {
        //         sum += this[i];
        //       }
        //     }
        //     return sum;
        //   }
        // }
    </script>

第四章:继承

对象之间的继承

对象之间的继承不是真正的继承,称为对象的拷贝。真正的继承是类型和类型之间的关系

  <script>
    // ...方法1:复制对象的成员给另一个对象...

    var wjl = {
      name: '王健林',
      money: 10000000,
      cars: ['玛莎拉蒂', '特斯拉'],
      houses: ['别墅', '大别墅'],
      play: function () {
        console.log('打高尔夫');
      }
    }

    var wsc = {
      name: '王思聪'
    }


    for (var key in wjl) {
      // 不给wsc复制同名的属性(判断wsc[key]有没有这个属性,如:name)
      if (wsc[key]) {
        continue; // 不执行这个属性循环,继续下一个循环
      }
      wsc[key] = wjl[key];
    }

    console.dir(wsc); // 此时:wsc对象就有wjl(除name属性之外的所有属性和方法)


    // ..............方法2:对象的拷贝...........................
    // 复制对象的成员给另一个对象; 将parent对象复制给child对象

         var wjl = {
             name: '王健林',
            money: 10000000,
             cars: ['玛莎拉蒂', '特斯拉'],
             houses: ['别墅', '大别墅'],
             play: function() {
                 console.log('打高尔夫');
             }
         }

         var wsc = {
             name: '王思聪'
         }
		 // 函数封装
         function extend(parent, child) {
             for (var key in parent) {
                 // 不给wsc复制同名的属性
                 if (child[key]) {
                     continue; // 不执行这个属性循环,继续下一个循环
                 }
                 child[key] = parent[key];
             }
         }

         extend(wjl, wsc);
         console.dir(wsc);
  </script>

call和bind的使用

常规方法:this指向

// .............常规方法:求两个数的和..............

    function fn(x, y) {
        console.log(this); // 函数中的this指向函数的调用者,window
        console.log(x + y); // 11
    }

    window.fn(5, 6);

call方法:

  • call() 改变函数中的this,直接调用函数
  • call的第一个参数:fn的this指向谁
	// ............call方法:............
    //    call()  改变函数中的this,直接调用函数
    //    call的第一个参数:fn的this指向谁

    function fn1(x, y) {
        console.log(this); // 此时this指向对象w {name: "zy"}
        console.log(x + y); // 5
    }

    var w = {
        name: 'zy'
    };

    fn1.call(w, 2, 3);

bind方法:

  • bind() 改变函数的this,并且返回一个新的函数 (但是bind不调用函数)
  • bind的第一个参数:fn的this指向谁
 
    // ..............bind方法:..............
    //      bind()   改变函数的this,并且返回一个新的函数 (但是bind不调用函数)
    //      bind的第一个参数:fn的this指向谁

    function fn(x, y) {
      console.log(this); // 此时this指向对象o {name: "zs"}
      console.log(x + y); // 7
    }
    var o = {
      name: 'zs'
    };
    // 此时this指向对象o
    fn.bind(o, 5, 2)(); // 返回一个新的函数 (不调用函数);后面的()表示自调用
  

构造函数的属性继承:借用构造函数

  <script>
    // 借用构造函数:可以解决原型继承的问题 => 无法设置构造函数的参数
      
    // 父类型
    function Person(name, age, sex) {

      this.name = name;  // 为Student学生对象添加name属性
      this.age = age;    // 为Student学生对象添加age属性
      this.sex = sex;    // 为Student学生对象添加sex属性
      // this.sayHi      

    };

    // 借用构造函数,无法实现继承父类型的方法
    Person.prototype.sayHi = function () {
      console.log(this.name);
    };

    // 子类型
    function Student(name, age, sex, score) {
      // 借用构造函数继承属性成员
      Person.call(this, name, age, sex); // 此时的this是Student学生对象
      this.score = score;
    };

    var s1 = new Student('zs', 18, '男', 100);
    console.dir(s1); 
	// 打印结果如下:Student里面就包含:父类中的属性,
	// 如:name、age、sex (此时里面没有sayHi方法,所以:借用构造函数,无法实现继承父类型的方法)
    // Student
    //   age: 18
    //   name: "zs"
    //   score: 100
    //   sex: "男"
  </script>

组合继承

  <script>
     // 组合继承:借用构造函数 + 原型继承
    // 1.借用构造函数存在的问题:只能够继承父类型的属性,无法继承父类型的方法
    // 2.原型继承:设置原型为父类型的对象;可以实现继承父类型的方法

    // 父类型 -- 人类对象
    function Person(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    // 所有子类型中都要有sayHi方法:子类型中共享的方法可以定义到父类型上
    Person.prototype.sayHi = function () {
      console.log('大家好,我是' + this.name);
    }

    // 子类型 -- 学生对象
    function Student(name, age, sex, score) {
      // 我们想让Student也具有父类型中的name, age, sex这些属性
      // 所以我们可以通过借用构造函数:将Person当成普通的函数
      // 通过call进行调用,改变this的指向;将Person中的this指向Student对象
      // 然后把相应的属性传给Student对象
      Person.call(this, name, age, sex);

      this.score = score;
    }

    // 通过原型让子类型继承父类型中的方法
    Student.prototype = new Person();
    Student.prototype.constructor = Student;
    // 学生特有的方法
    Student.prototype.exam = function () {
      console.log('考试');
    }

    var s1 = new Student('zs', 18, '男', 100);
    console.dir(s1); // 只要学生有exam方法


    var p1 = new Person('ls', 18, '男');
    console.dir(p1); // 里面没有学生的exam方法

    // 子类型 -- 老师对象
    function Teacher(name, age, sex, salary) {
      // 借用构造函数
      Person.call(this, name, age, sex);

      this.salary = salary;
    }

    // 通过原型让子类型继承父类型中的方法
    Teacher.prototype = new Person();
    Teacher.prototype.constructor = Teacher;

    var t1 = new Teacher('ww', 30, '男', 100000);
    console.dir(t1); // 里面没有学生的exam方法

    t1.sayHi(); // 调用父类的sayHi方法

  </script>

原型分析图

在这里插入图片描述

构造函数的原型方法继承:拷贝继承(for-in)

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

Person.prototype.sayName = function () {
  console.log('hello ' + this.name)
}

function Student (name, age) {
  Person.call(this, name, age)
}

// 原型对象拷贝继承原型对象成员
for(var key in Person.prototype) {
  Student.prototype[key] = Person.prototype[key]
}

var s1 = Student('张三', 18)

s1.sayName() // => hello 张三
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落花流雨

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值