美文网首页JavaScript
第十七节JavaScript 构造函数与原型

第十七节JavaScript 构造函数与原型

作者: 心存美好 | 来源:发表于2021-12-06 15:04 被阅读0次

    一. JS对象复习

    1. 对象的创建

    1.1. 字面量方式(也叫直接量)

    var obj = {}; 
    

    1.2 new操作符创建对象

    var obj = new Object()
    
    2.对象属性和方法

    2.1 对象的属性

    对象有属性,所谓的属性就是这个对象的特点、特性

    var student = {};
    student.sex = '男孩'
    

    2.2 对象的方法

    对象只能有属性,只是如果这个属性的值是一个函数,那么这个函数我们就称为对象的“方法”(method)。

    student.study = function(){
        console.log('我学会了语文')
    }
    

    二. 构造函数

    构造函数就是用来创造新对象的, 它必须用关键字new来创造,如果将构造函数用作普通函数的话,往往不会正常工作的. 按照一惯的约定, 我们开发者把构造函数的首字母大写用作辨别. 一个构造函数创造的对象被称为该构造函数的实例

    常见的内置构造函数:

    Object()

    Array()

    RegExp()

    Function()

    Date() //创建日期对象

    1. 构造函数的原理

    JavaScript规定,一个函数可以用new关键字来调用。那么此时将按顺序发生四件事情:

    1)隐秘的创建一个新的空对象

    2)将这个函数里面的this绑定到刚才创建隐秘新对象上

    3)执行函数体里面的语句

    4)返回这个新的对象

    例子:

    function People(){
        this.name = "小明";
        this.age = 18;
        this.sex = "男"; 
    }// 使用new关键字来调用函数,那么函数就叫做构造函数,将返回一个对象
    var xiaoming = new People();
    
    console.log(xiaoming);  // People {name: "小明", age: 18, sex: "男"}
    console.log(xiaoming.age);  //18
    console.log(typeof xiaoming); //object
    

    构造函数是使用new关键字来调用,生成实例对象

    厉害了,现在我们发现函数不仅仅能够执行,还能返回出来一个对象。也就是说,对象是函数“生”出来的,对象是函数“new”出来的。

    所以我们称呼这个生出对象的函数,叫做构造函数,一般的,构造函数用大写字母开头。也叫作People“类”。

    xiaoming是People类的实例对象。

    People是xiaoming对象的构造函数,也叫类。

    2. 构造函数类和实例

    People这个函数将被new关键词来调用,所以称为构造函数

    构造函数里面语句将会被执行返回一个对象,所以new多少次,里面语句就执行多少次,宏观看,返回的所有对象都有name的属性,age属性

    类是具有相同属性和方法的集合

    我们说,这些返回的对象都有相同的属性群,所以可以看做是一类东西,那么People这个构造函数,也就可以看成类的定义

    例子:

    function People(name,age,sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    
    //new关键字造出来的xiaoming、xiaohong,我们称为People类的实例。可以理解为实体
    var xiaoming = new People("小明",18,"男");
    var xiaohong = new People("小红",17,"女");
    
    console.log(xiaoming);
    console.log(xiaohong);
    

    但是JavaScript中,没有类的概念。是通过构造函数的4步走机制来创建类的对象,可以看为类。JS这个语言是“基于对象”(base Object)的语言,不能叫做“面向对象”(orinted object)的语言。

    所以我们的构造函数,也可以看成类的定义:

    构造函数其实就是一个普通的函数,JS没有规定函数里写什么,。只不过我们知道new操作符的原理后,就习惯了先用this.'...' = 来罗列所有属性,和方法而已。你一定要深刻理解:

    new调用一个函数的时候,函数里面的语句会执行。

    function People(name,age,sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.sayHello = function(){
            alert("你好我是" + this.name + ",nice to meet you");
        }
    }
    
    function Dog(name,pinzhong,age,sex){
        this.name = name;
        this.pinzhong = pinzhong;
        this.age = age;
        this.sex = sex;
        this.wangwang = function(){
            alert("汪汪汪汪" + this.name + "汪汪汪汪汪汪");
        }
    }
    
    var xiaoming = new People("小明",11,"男");
    xiaoming.sayHello();
    
    var xiaobai = new Dog("小白","京巴",2,"公的");
    xiaobai.wangwang();
    
    3. 构造函数里面的注意事项

    3.1 如果构造函数里没有this,那么将创建一个空对象

    //构造函数里面如果没有了this,那么就废了,就不能给隐秘新创建出来的对象添加属性。但是里面的语句能够执行!!所以new出来的就是一个空对象
    function People(){
        for(var i = 1 , sum = 0; i <= 100 ; i++){
            sum += i;
        }
        alert(sum);
    }
    
    var xiaoming = new People();
    console.log(xiaoming);
    

    3.2 构造函数构造函数中,不允许出现return语句

    构造函数总不能出现return, 严格的说是不能return 引用类型数据.如果出现return语句返回引用类型值,那么构造函数将不能返回新创建的那个对象,而是返回return 语句后面的内容

    function People(name,age){
        this.name = name;
        this.age = age;
        return {"a":1,"b":2};
    }
    
    var xiaoming = new People("小明",12);
    console.log(xiaoming);
    
        function aa() {
          this.name = "mingzi";
          function bb() {
            console.log("我是BB");
          }
          console.log("试试构造函数");
          return bb;   //返回了 bb函数  覆盖了原有this对象,数组也不行,也是对象的一种
        }
        var xm = new aa();
        console.log(xm);
    

    3.3 JS没有规定构造函数中的语句的规范,想怎么写就怎么写:

    // 这就是函数 你想怎么写就怎么写
    function People(name,age){
      this.name = name;
      this.age = age;
      // if(this.age >= 18){
      //  this.state = "成年人";
      // }else{
      //  this.state = "未成年";
      // }
    
      this.state = this.age >= 18 ? "成年人" : "未成年";
    }
    
    var xiaoming = new People("小明",11);
    alert(xiaoming.state);
    
    

    总结一下

    当一个函数用()调用的时候,this就是window

    当一个函数用对象调用的时候,this就是这个对象

    当一个函数绑定给一个HTML元素事件的时候,this就是这个HTML元素

    当一个函数用定时器调用的时候,this就是window

    当一个函数用apply、call调用的时候,this就是你指定的第一个参数

    当一个函数用new调用的时候,this就是隐秘创建的空对象,函数里面的语句将被执行,并且返回新对象

    四. 原型 prototype

    1 原型 prototype

    prototype就是英语“原型”的意思。原型是构造函数创建对象的原始模型

    原型的特点:

    原型也是对象,原型是函数对象的一个属性
    原型自带constructor属性, constructor指定构造函数
    构造函数创建出的对象会继承原型的属性和方法

    // 任何函数都有原型,只是原型对于普通函数没什么大用,但对于构造函数用处极大
    function fun(){
     console.log("您好")
    }
    
    console.log(fun.prototype);
    console.log(typeof fun.prototype);
    
    

    在JavaScript中,任何一个函数,都有一个prototype属性,指向一个对象。打印prototype属性,你会发现这个属性指向一个空对象。打印prototype的类型,发现是object类型。

    2. 原型对象与实例

    一个函数的原型,对于普通函数来说,没任何用。但是如果函数是一个构造函数,那么函数的原型,用处极大!

    示例:

    function People(name,sex,age){
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
    People.prototype = {
        hight: 180
    }
    
    var xiaoming = new People("小明","男",18);
    
    console.log(xiaoming.name);  // "小明"
    console.log(xiaoming.sex);   // "男"
    console.log(xiaoming.age);   // 18
    console.log(xiaoming.hight); // 180
    

    所以我们甚至可以把所有的属性都加在原型上

    // 构造函数
    function People(){
    }
    
    //构造函数的原型,我们更改了构造函数的原型,为一个新的对象:
    People.prototype = {
        name : "小明",
        sex : "男",
        age : 18,
        hight: 180
    }
    var xiaoming = new People();
    
    console.log(xiaoming.name);  // "小明"
    console.log(xiaoming.sex);   // "男"
    console.log(xiaoming.age);   // 18
    console.log(xiaoming.hight); // 180
    

    当一个对象被new出来的时候,不仅仅执行了构造函数里面的语句,我们的感觉,构造函数的原型中,所有的属性也加给了这个对象

    3. 实例对象的proto 属性

    当一个对象被new出来的时候,不仅仅执行了构造函数里面的语句,也会把proto指向构造函数的prototype。

    // 构造函数
    // 构造函数里面没有任何语句,也就是说,这个构造函数在执行的时候,不会给创建出来的对象添加任何属性。
    function People(){
    }
    
    //构造函数的原型,我们更改了构造函数的原型,为一个新的对象:
    People.prototype = {
        name : "小明",
        sex : "男",
        age : 18,
        hight: 180
    }
    
    //当一个对象被new出来的时候,不仅仅执行了构造函数里面的语句,也会把__proto__指向构造函数的prototype。
    var xiaoming = new People();
    
    
    //当我们试图访问sex、age属性的时候,身上没有。那么就去查找原型,原型身上有,就当做了自己的属性返回了。
    console.log(xiaoming.name); 
    console.log(xiaoming.sex); //自己没有,找原型对象
    console.log(xiaoming.age); //自己没有,找原型对象
    
    console.log(xiaoming.__proto__); 
    // {name: "小明", sex: "男", age: 18, hight: 180}
    
    console.log(xiaoming.__proto__ == People.prototype);
    // true
    

    注意:

    任何一个对象,都有proto属性,这个属性是Chrome自己的属性,别的浏览器不兼容,但是别的浏览器也有原型对象,只不过不能通过proto进行访问而已。

    这是属性指向自己的原型对象。

    构造函数、原型、实例对象之间的关系

    <style>
      #box {
        font: size 32px;
        color: red;
      }
    </style>
    </head>
    <body>
      <h1></h1>
      <div id="box">wo shi box
      </div>
      <script>
        //1、原型与构造函数之间的关系
        function people(name, age) {
          this.name = name;
          this.age = age;
        }
        console.dir(people)
        console.dir(people.prototype.constructor === people);  //通过函数的属性prototype找到原型,再通过原型.constructor找到函数自己 
        console.dir(people.prototype.constructor);
        console.dir(people);
        console.log(people.prototype.constructor);
        // prototype对象是people构造函数的原型
        // people函数prototype原型对象的构造函数
    
        //2、构造函数与实例对象的关系
        var xm = new people('小明', 18)
        console.log('xm', xm);
        // xm是函数people的实例对象
        //people函数时xm对象的构造函数
    
        //3、实例对象与原型对象的关系
        console.dir(xm.__proto__ === people.prototype);//实例对象的__proto__属性等于原型
      </script>
    
    
    1. 原型的扩展

    我们的JavaScript有一个棒的机制:原型链查找。

    当我们试图访问一个对象身上的属性的时候,如果这个对象身上有这个属性,则返回它的值。如果它身上没有这个属性,那么将访问它的原型对象,检测它的原型对象身上是否有这个值,如果有返回它原型对象身上的这个值。

    任何一个函数都有原型,原型是一个对象,用prototype来访问。

    当这个函数是构造函数的时候,new出来的对象,它们的原型对象就是这个构造函数的原型。

    prototype我们称为“原型”,只有函数有原型

    proto我们称为“原型对象”,任何对象都有原型对象。

    1. 原型的应用

    我们定义一个方法的时候,如果写在构造函数里面:

    function People(name,age){
        this.name = name;
        this.age = age;
        this.sayHello = fucntion(){
            alert("你好,我是" + this.name + "我今年" + this.age + "岁了");
        }
    }
    
    var xiaoming = new People("小明",12);
    var xiaohong = new People("小红",11);
    
    xiaoming.sayHello();
    xiaohong.sayHello();
    

    这样写可以,但是有什么问题呢,内存开太大

    在new一个xiaoming的时候,构造函数中的代码顺序执行,绑定了name,age,sayHello,new一个xiaohong的时候也是如此,你有没有发现,不同的对象拥有相同方法.因此可以将复用的方法放在原型对象上.

        function People(name,age){
            //构造函数里面,负责定义一些属性,随着构造函数的执行,这些属性将绑定到new出来的对象身上
            this.name = name;
            this.age = age;
        }
        //把相同的方法,定义在原型对象身上:
        People.prototype.sayHello = function(){
            alert("你好,我是" + this.name + "我今年" + this.age + "岁了");
        }
        var xiaoming = new People("小明",12);
        var xiaohong = new People("小红",11);
        
        alert(xiaoming.sayHello == xiaohong.sayHello); //true
    
    <style>
      #box {
        font: size 32px;
        color: red;
      }
    </style>
    </head>
    
    <body>
      <h1></h1>
      <div id="box">wo shi box
      </div>
      <script>
        //函数实例化对象,所有共用的属性就可以放到原型上
        function people(name, age) {
          this.name = name;
          this.age = age;
        }
        var xm = new people('小明', 19);
        var xh = new people('小红', 21);
        console.log(xm.__proto__ === xm.__proto__);  //xh xm是同一个原型
        console.log(xm.sayhello === xh.sayhello);//xh xm sayhello函数不同 将sayhello放到原型上就相等了
        people.prototype.sayhello = function () {
          console.log("大家好");
        }
          console.log(xm.__proto__.constructor);  //实例对象通过原型找到构造函数
      </script>
    

    证明了xiaoming的sayHello方法和xiaohong的,是同一个函数。内存消耗小很多。

    示例:

    function Grand(){}
    Grand.prototype.sex = "male";
    var grand = new Grand();
    
    function Father(){
        this.age = 50;
    }
    Father.prototype = grand;
    var father = new Father();
    
    function Son(){
        this.name = "wuwei";
    }
    Son.prototype = father;
    var son = new Son();
    console.log(son.name);
    console.log(son.age);
    console.log(son.sex);
    console.log(son.toString()); //[object Object]
    

    五.创建对象的常见的几种模式

    1、工厂模式

    ⼯⼚模式是软件⼯程领域⼀种⼴为⼈知的设计模式,这种模式抽象了创建 具体对象的过程

    function createPerson(name,age,family) {
        var o = new Object();
        o.name = name;
        o.age = age;
        o.family = family;
        o.say = function(){
            alert(this.name);
        }
        return o;
    }
    
    var person1 =  createPerson("lisi",21,["lida","lier","wangwu"]);   //instanceof无法判断它是谁的实例,只能判断他是对象,构造函数都可以判断出
    var person2 =  createPerson("wangwu",18,["lida","lier","lisi"]);
    console.log(person1 instanceof Object);                           //true
    console.log(person1 instanceof createPerson); //false 工厂模式这个对象不知道谁创建出来的自己,instanceof判断前面的对象是不是后面的函数构造出来的
    

    ⼯⼚模式虽然解决了创建多个相似对象的问 题,但没有解决对象识别的问题(但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,本例中,得到的都是o对象,对象的类型都是Object,因此出现了构造函数模式)。工厂模式是自己写的结构,构造函数模式自带结构。

    2、构造函数模式
    function Person(name,age,family) {
        this.name = name;
        this.age = age;
        this.family = family;
        this.say = function(){
            alert(this.name);
        }
    }
    var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
    var person2 = new Person("lisi",21,["lida","lier","lisi"]);
    console.log(person1 instanceof Object); //true
    console.log(person1 instanceof Person); //true
    console.log(person2 instanceof Object); //true
    console.log(person2 instanceof Person); //true // 判断xm是通过哪个函数创建出来的对象
    console.log(person1.constructor);      //constructor 属性返回对创建此对象的数组、函数的引用
    

    对比工厂模式有以下不同之处:

    1、没有显式地创建对象

    2、直接将属性和方法赋给了 this 对象

    3、没有 return 语句

    可以看出,构造函数知道自己从哪里来(通过 instanceof 可以看出其既是Object的实例,又是Person的实例)

    构造函数模式的缺点: 就是每个⽅法都要在每个实例上重新创建⼀遍

    3、原型模式

    借助函数的原型,将一些实例对象共享的属性和方法放在原型对象中, 这样就不必在构造函数中定义重复的方法

      function Person() {
      }
      
      Person.prototype.name = "lisi";
      Person.prototype.age = 21;
      Person.prototype.family = ["lida","lier","wangwu"];
      Person.prototype.say = function(){
          alert(this.name);
      };
     console.log(Person.prototype);   //Object{name: 'lisi', age: 21, family: Array[3]}
     
     var person1 = new Person();        //创建一个实例person1
     console.log(person1.name);        //lisi
     
     var person2 = new Person();        //创建实例person2
     person2.name = "wangwu";
     person2.family = ["lida","lier","lisi"];
     console.log(person2);            //Person {name: "wangwu", family: Array[3]}
     // console.log(person2.prototype.name);         //报错
     console.log(person2.age);              //21
    

    原型模式的好处是所有对象实例共享它的属性和方法(即所谓的共有属性),
    缺点就是省略了为构造函数传递初始值参数,导致所有的实例对象都是相同的属性和方法,

    4、混合模式(构造函数模式+原型模式)

    构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性

        function people(name, age) {
          this.name = name;
          this.age = age;
        }
        //没有修改构造函数默认的原型对象,有默认的constructor属性,是灰色的。但是添加属性不方便
        people.prototype.play = function () {
          "我会王者荣耀"
        }
        people.prototype.like = function () {
          "我喜欢看书,乒乓球"
        }
        var xh = new people("小红", 19);
        var xm = new people("小明", 18);
        console.log(xm);
        console.log(xh
        )
    
    
    
    
    
    
        function Person(name, age, family) {
          this.name = name;
          this.age = age;
        }
        Person.prototype = {
          constructor: Person,  //将一个对象赋值给prototype对象,constructor属性需要手动添加,不是默认的。每个函数都有prototype属性,指向该函数原型对象,原型对象都有constructor属性,这是一个指向prototype属性所在函数的指针
          say: function () {
            alert(this.name);
          },//对象属性之间用,号
          play: function () {
            console.log("我很喜欢玩游戏");
          }
        }
        var person1 = new Person("lisi", 21);
        console.log(person1);
        var person2 = new Person("wangwu", 21);
        console.log(person2);
    

    可以看出,混合模式共享着对相同方法的引用,又保证了每个实例有自己的私有属性。最大限度的节省了内存

    高程中还提到了动态原型模式,寄生构造函数模式,稳妥构造函数模式, 这些就自己读一读, 了解一下

    相关文章

      网友评论

        本文标题:第十七节JavaScript 构造函数与原型

        本文链接:https://www.haomeiwen.com/subject/yeogxrtx.html