面向对象-构造函数、原型、原型链

JS基本介绍

  • JS的用途:Javascript可以实现浏览器端、服务器端(nodejs)。。。
  • 浏览器端JS由以下三个部分组成:
    • ECMAScript:基础语法(数据类型、运算符、函数。。。)
    • BOM(浏览器对象模型):window、location、history、navigator。。。
    • DOM(文档对象模型):div、p、span。。。
  • ECMAScript又名es,有以下重大版本:
    • 旧时代:
      • es1.0。。。es3.1
    • 新时代:
      • es5
      • es6(es2015)
      • es7(es2016)、es8(es2017)

数据类型

  • 基本数据类型(值类型):(数字、字符串、布尔值、null、undefined)
    • undefined类型?
      1.声明了一个变量,但没有赋值,值默认为undefined, 如 var a;
      2.声明一个变量了,并赋值了一个undefined的值,如 var b = undefined;
      3.一个对象中,获取某个不存在的属性,值为undefined
  • 复杂数据类型(引用类型):(对象)
    • 数组
    • 函数
    • 正则表达式
    • Date

对象的基本使用

创建一个对象

    var student={ 
        name:"李白" , //student有一个name属性,值为"李白"
        grade:"初一" ,
        //a、student有一个say属性,值为一个函数
        //b、student有一个say方法
        say:function(){
            console.log("你好");
        },
        run:function(speed){
            console.log("正在以"+speed+"米/秒的速度奔跑");
        }
    }

对象是键值对的集合:对象是由属性和方法构成的 (ps:也有说法为:对象里面皆属性,认为方法也是一个属性)

  • name是属性 grade是属性
  • say是方法 run是方法

对象属性操作

获取属性:

第一种方式:.语法

  • student.name 获取到name属性的值,为:"李白"
  • student.say 获取到一个函数

第二种方式:[]语法

  • student["name"] 等价于student.name
  • student["say"] 等价于student.say

注意:2种方式的差异:

  • .语法更方便,但是坑比较多(有局限性),比如:
    • .后面不能使用js中的关键字、保留字(class、this、function。。。)
    • .后面不能使用数字
    var obj={};
    obj.this=5; //语法错误
    obj.0=10;   //语法错误
  • []使用更广泛
    • o1[变量name]
    • ["class"]、["this"]都可以随意使用 obj["this"]=10
    • [0]、[1]、[2]也可以使用
      • obj[3]=50 = obj["3"]=50
    • 甚至还可以这样用:["[object Array]"]
      • jquery里面就有这样的实现
    • 也可以这样用:["{abc}"]
      • 给对象添加了{abc}属性

设置属性

  • student["gender"]="男" 等价于: student.gender="男"
    • 含义:如果student对象中没有gender属性,就添加一个gender属性,值为"男"
    •  如果student对象中有gender属性,就修改gender属性的值为"男"
      
  • 案例1:student.isFemale=true
  • 案例2:student["children"]=[1,2,5]
  • 案例3:
    student.toShanghai=function(){   
        console.log("正在去往上海的路上")   
    }

删除属性

  • delete student["gender"]
  • delete student.gender

通过构造函数创建对象

构造函数创建对象的例子:

  • var xiaoming = new Object() --> var xiaoming = {};

  • var now = new Date()

  • var rooms = new Array(1,3,5) --> var rooms = [1,3,5]

  • var isMale=/123/; ==> var isMale=new RegExp("123")

    • isMale是通过RegExp构造函数创建出来的对象
    • isMale是RegExp构造函数的实例
  • 以上例子中,Object、Date、Array都是内置的构造函数

自定义一个构造函数来创建对象

  • 构造函数
    function Person(name,age){
        this.name=name;
        this.age=age;
    }
    var p1=new Person("赵云",18)

等同于

    function person(name ){
        Object obj = new Object();
        obj.name  = name;
        obj.age  = age;
        return obj;
    }
    var p1 = new Person("赵云",18)
  • 说明:p1就是根据Person构造函数创建出来的对象

构造函数的概念

  • 在JavaScript中,用new关键字来调用的函数,称为构造函数。构造函数首字母一般大写(PS:非强制,但这么写有助于区分构造函数和普通函数);
  • 任何函数都可以当成构造函数
    function CreateFunc(){ }
  • 只要把一个函数通过new的方式来进行调用,我们就把这一次函数的调用方式称之为:构造函数的调用
    • new CreateFunc(); 此时CreateFunc就是一个构造函数
    • CreateFunc(); 此时的CreateFunc并不是构造函数
1)构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用new 运算符来调用,否则就是普通函数。
2)this就是代表当前作用域对象的引用。如果在全局范围this 就代表window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。

关于new Object()

  • new Object()等同于对象字面量{}

构造函数 new 的执行过程

var p1 = new Person();

  • 1、new在内存中创建一个空的对象 (我们把这个对象称之为Person构造函数的实例)- p1
  • 2、让构造函数中的this指向这个新的对象-(p1)
  • 3、执行函数内部的代码,其中,操作this的部分就是操作了该实例(p1)
  • 4、返回值:
    • a、如果函数没有返回值(没有return语句),那么就会返回构造函数的实例(p1)
    • b、如果函数返回了一个基本数据类型的值,那么本次构造函数的返回值是该实例(p1)
        function fn(){
            ···
        }
        var f1 = new fn();    //f1就是fn的实例
    
        function fn2(){
            return "abc";
        }
        var f2 = new fn2();   //f2是fn2构造函数的实例
    
    • c、如果函数返回了一个复杂数据类型的值,那么本次函数的返回值就是该值
        function fn3(){
            return [1,3,5]; 
            //数组是一个对象类型的值,
            //所以数组是一个复杂数据类型的值
            //-->本次构造函数的真正返回值就是该数组
            //-->不再是fn3构造函数的实例
        }
        var f3=new fn3();   //f3还是fn3的实例吗?错
        //f3值为[1,3,5]
    

注意:
- f1 和 f2会自动含有一个constructor属性,指向它们的构造函数
console.log(f1.constructor == fn); //true console.log(f2.constructor == fn2); //true
- Javascript还提供了一个instanceof运算符,验证原型对象与实例对象之间的关系
console.log(f1 instanceof fn); //true console.log(f2 instanceof fn2); //true

构造函数模式的问题

构造函数方法很好用,但是存在一个泄露内存(浪费内存)的问题。如下:

    function Person(name,age){
        this.name=name;
        this.age=age;
        this.say=function(){}
    }
    var p1=new Person();
    var p2=new Person();
    var p3=new Person();
    var p4=new Person();
    ...    

    //p1对象和p2对象的say方法是否是同一个方法:false
    console.log(p1.say === p2.say);
    //由于say方法功能相似,但是不是同一个方法(没有指向同一块内存,会造成内存浪费)

    //解决方案1:(弊端:如果有多个需要共享的函数的话就会造成全局命名空间冲突的问题。)
    function say(){
        ···
    }
    function Person(name,age){
        this.name = name;
        this.age = age;
        this.say = say;
    }

    //解决方案2:(基本上解决了构造函数的内存浪费问题。但是代码看起来有些许的格格不入···)
    var fns = {
        say: function(){
            console.log("你好");
        },
        run: function(){
            console.log("时速500KM");
        }
    }
    function Person(name,age){
        this.name = name;
        this.age = age;
        this.say = fns.say;
        this.run = fns.run;
    }

    //解决方案3(很完美!):把say方法写在他们共同的(父对象)中,他们共同的父对象,就可以通过:Person.prototype来获取
    //-->只要把say方法写在Person.prototype中,那么say方法就是同一个方法(所有对象实例需要共享的属性和方法直接定义在 `prototype` 对象上)
    Person.prototype.say = function(){
        console.log('你好');
    }
    Person.prototype.run = function(){
        console.log('时速500KM');
    }
    Person.prototype.sex = "男";
    p1.run();
    p2.run();

    //验证p1.run和p2.run是否是同一个方法?
    //这时所有实例的sex属性和say(),eat()方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。
    console.log(p1.run == p2.run); //true 指向同一个方法,这种方法避免了内存的浪费
    console.log(p1.run == Person.prototype.run);//true

    var p3=new Person();
    console.log(p3.run == p1.run); //true
    console.log(p3.run === p1.run);//true
    //结论:只要往某个构造函数的prototype对象中添加某个属性、方法,那么这样的属性、方法都可以被所有的构造函数的实例所共享
    //==>这里的【构造函数的prototype对象】称之为原型对象
    //  Person.prototype是 p1 p2 p3 的原型对象
    //  Person.prototype是Person构造函数的【实例】的原型对象

    //  Person的原型对象是谁呢?
    //  -->首先要知道Person的构造函数:-->Function
    //  -->所以Person的原型对象是:Function.prototype

    //  p1的原型对象是谁呢?
    //  -->首先要知道p1是谁创建的?    -->Person
    //  -->所以p1的原型对象时:     Person.prototype


  • JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的所拥有。这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。
    Person.prototype={
        constructor: Person,
        say:function(){
            console.log("你好");
        },
        run:function(){
            console.log("时速500KM");
        }
    }
  • 注意:
  1. 一般情况下,应该先改变原型对象,再创建对象
  2. 一般情况下,对于新的原型对象,会添加一个constructor属性,从而不破坏原有的原型对象的结构

Prototype模式的验证方法

为了配合prototype属性,Javascript定义了一些辅助方法,帮助我们使用它。

  • isPrototypeOf() 这个方法用来判断,某个proptotype对象和某个实例之间的关系。
      console.log(Person.prototype.isPrototypeOf(p1)); //true
    
  • hasOwnProperty() 每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。
      console.log(p1.hasOwnProperty('name')); //true
      console.log(p1.hasOwnProperty('sex')); //false
    
  • in 运算符
    • 可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。
      console.log('name' in p1); //true
      console.log('sex'in p1); //true
    
    • 还可以用来遍历某个对象的所有属性。
      for(var prop in p1) { console.log("p1[" + prop + "]=" + p1[prop]); }
    

构造函数、原型对象、实例 三者关系:

  • 任何函数都具有一个 prototype 属性,该属性是一个对象
  • 构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数
  • 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto____proto__ 是非标准属性。)
  • 所有实例都直接或间接继承了原型对象的成员

原型链

  • 概念:通过继承来实现的一套关系谱,继承的核心就是通过__proto__属性将若干个对象连接起来。
  • 根本:继承
  • 属性:每一个对象(null除外)都具有的一个__proto__属性,指向该对象的原型(父对象)
  • 意义:可以实现让该对象访问到父对象中相关属性
  • 根对象:Object.prototype
var arr=[1,3,5]
arr.proto:Array.prototype
arr.proto.__proto__找到根对象
    function Animal(){}
    var cat=new Animal();
    //cat.__proto__ 指向Animal.prototype
    //cat.__proto__.__proto__ 指向根对象
posted @ 2020-05-16 14:19  十一是假期啊  阅读(206)  评论(0编辑  收藏  举报