Skip to content

一、构造函数

1. 用new调用函数的四步走

js
 new 函数();
  • JS规定,使用new操作符调用函数会进行"四步走":

    • 函数体内会自动创建出一个空白对象
    • 函数的上下文(this)会指向这个对象
    • 函数体内的语句会执行
    • 函数会自动返回上下文对象,即使函数没有return语句
  • 四步走详解

    js
      function fun() {
           this.a = 3;
           this.b = 5;
       }
       var obj = new fun();
       console.log(obj);
    • 第1步:函数体内会自动创建出一个空白对象。 obj-1.png

    • 第2步:函数的上下文(this)会指向这个对象。 obj-2.png

    • 第3步:执行函数体中的语句
      obj-3.png

    • 第4步:函数会自动返回上下文对象,即使函数没有return语句。 obj-4.png

2. 构造函数

  • 用new调用一个函数,这个函数就被称为"构造函数",任何函数都可以是构造函数,只需要用new调用它。

  • 顾名思义,构造函数用来“构造新对象”,它内部的语句将 为新对象添加若干属性和方法,完成对象的初始化

  • 构造函数必须用new关键字调用,否则不能正常工作,正因 如此,开发者约定构造函数命名时首字母要大写

  • 一个函数是不是构造函数,要看它是否用new调用,而至于名称首字母大写,完全是开发者的习惯约定。

  • 如果不用new调用构造函数

    js
      function People(name, age, sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
      } 
      People('小明', 12, '男');
      People('小红', 10, '女');
      People('小刚', 13, '男');
    • 结果:都会成为全局的变量,且变量的值会依次覆盖,就是小刚 13 男
  • 使用new构建

    js
      function People(name, age, sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
      }
      var xiaoming = new People('小明', 12, '男');
      var xiaohong = new People('小红', 10, '女');
      var xiaogang = new People('小刚', 13, '男');
  • 为对象添加方法

    js
      function People(name, age, sex) { 
          this.name = name;
          this.age = age;
          this.sex = sex;
          this.sayHello = function () {
              console.log('我是' + this.name + ',我' + this.age + '岁了');
          }; 
       }
      var xiaoming = new People('小明', 12, '男'); 
      var xiaohong = new People('小红', 10, '女'); 
      var xiaogang = new People('小刚', 13, '男'); 
      xiaoming.sayHello();
      xiaohong.sayHello();
      xiaogang.sayHello();
  • 构造函数中的this不是函数本身

3. 类和实例

obj-5

  • Java、C++等是"面向对象"(object-oriented)语言。
  • JavaScript是"基于对象"(object-based)语言。
  • JavaScript中的构造函数可以类比于OO语言中的"类", 写法的确类似,但和真正OO语言还是有本质不同, JS和其他OO语言完全不同的、特有的原型特性。

4. prototype和原型链查找

  • 什么是prototype

    • 任何函数都有prototype属性,prototype是英语"原型"的意思。
    • prototype属性值是个对象,它默认拥有constructor属性指回函数obj-7
    • 普通函数来说的prototype属性没有任何用处,而构造函数的prototype属性非常有用。
    • 构造函数的prototype属性是它的实例的原型
  • 构造函数的prototype是实例的原型 obj-8

    html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <script>
            function People(name, age, sex) {
                this.name = name;
                this.age = age;
                this.sex = sex;
            }
            // 实例化
            var xiaoming = new People('小明', 12, '男');
            // 测试三角关系是否存在
            console.log(xiaoming.__proto__ === People.prototype);
        </script>
    </body>
    </html>
  • 原型链查找

    • 每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,形成一个原型链。 查找特定属性的时候,先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找… 这个操作被委托在整个原型链上,这个就是我们说的原型链了。

    • JavaScript规定:实例可以打点访问它的原型的属性和方法,这被称为"原型链查找"。

      js
          function People(name, age, sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
          }
          People.prototype.nationality = '中国';
          var xiaoming = new People('小明', 12, '男');
          console.log(xiaoming.nationality);
      • People.prototype.nationality在构造函数的prototype上添加nationality属性。
      • xiaoming.nationality实例可以打点访问原型的属性和方法。 obj-9
    • 原型的遮蔽效应

      js
          function People(name, age, sex) {
          this.name = name;
          this.age = age;
          this.sex = sex;
      }
      // 往原型上添加nationality属性
      People.prototype.nationality = '中国';
      
      // 实例化
      var xiaoming = new People('小明', 12, '男');
      var tom = new People('汤姆', 10, '男');
      tom.nationality = '美国';
      
      console.log(xiaoming.nationality);      // 中国
      console.log(xiaoming);
      
      console.log(tom.nationality);           // 美国
      //tom本身有nationality时,就不找原型上的nationality属性了,原型链的遮蔽效应
      //跟局部变量全局变量差不多

      obj-10

    • hasOwnProperty方法可以检查对象是否真正"自己拥有"某属性或者方法。

      js
          xiaoming.hasOwnProperty('name'); // true
          xiaoming.hasOwnProperty('age');  // true
          xiaoming.hasOwnProperty('sex');  // true
          xiaoming.hasOwnProperty('nationality'); // false
    • in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法。

      js
          'name' in xiaoming  // true
          'age' in xiaoming   // true
          'sex' in xiaoming   // true
          'nationality' in xiaoming  // true

5. 在prototype上添加方法

  • 把方法直接添加到实例身上的缺点:每个实例和每个实例的方法函数都是内存中不同的函数,造成了内存的浪费
  • 解决方法:将方法写到prototype上obj-11

6. 原型链的终点

obj-12

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        function People(name, age) {
            this.name = name;
            this.age = age;
        }
        var xiaoming = new People('小明', 12);

        console.log(xiaoming.__proto__.__proto__ === Object.prototype);     // true
        console.log(Object.prototype.__proto__);                            // null

        console.log(Object.prototype.hasOwnProperty('hasOwnProperty'));     // true
        console.log(Object.prototype.hasOwnProperty('toString'));           // true
    </script>
</body>
</html>
  • 关于数组的原型链 array

    html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <script>
            var arr = [344, 45, 34, 23];
    
            console.log(arr.__proto__ === Array.prototype);                 // true
            console.log(arr.__proto__.__proto__ === Object.prototype);      // true
            console.log(Array.prototype.hasOwnProperty('push'));            // true
            console.log(Array.prototype.hasOwnProperty('splice'));          // true
        </script>
    </body>
    </html>

6. 继承

  • JavaScript中如何实现继承?

    • 实现继承的关键在于:子类必须拥有父类的全部属性和方法,同时子类还应该能定义自己特有的属性和方法。
    • 使用JavaScript特有的原型链特性来实现继承,是普遍的做法
  • 通过原型链实现继承 arrayarray

    html
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    
    <body>
        <script>
            // 父类,人类
            function People(name, age, sex) {
                this.name = name;
                this.age = age;
                this.sex = sex;
            }
            People.prototype.sayHello = function () {
                console.log('你好,我是' + this.name + '我今年' + this.age + '岁了');
            };
            People.prototype.sleep = function () {
                console.log(this.name + '开始睡觉,zzzzz');
            };
    
            // 子类,学生类
            function Student(name, age, sex, scholl, studentNumber) {
                this.name = name;
                this.age = age;
                this.sex = sex;
                this.scholl = scholl;
                this.studentNumber = studentNumber;
            }
            // 关键语句,实现继承
            Student.prototype = new People();
    
            Student.prototype.study = function () {
                console.log(this.name + '正在学习');
            }
            Student.prototype.exam = function () {
                console.log(this.name + '正在考试,加油!');
            }
            // 重写、复写(override)父类的sayHello
            Student.prototype.sayHello = function () {
                console.log('敬礼!我是' + this.name + '我今年' + this.age + '岁了');
            }
    
            // 实例化
            var hanmeimei = new Student('韩梅梅', 9, '女', '慕课小学', 100556);
    
            hanmeimei.study();
            hanmeimei.sayHello();
            hanmeimei.sleep();
    
            var laozhang = new People('老张', 66, '男');
            laozhang.sayHello();
        </script>
    </body>
    
    </html>

Released under the MIT License.