• JS高级——原型链


    构造函数

    1. 构造函数是特殊的函数,里面没有returen返回值
    2. new的过程中,会自动将对象返回,不需要return
    3. new的过程中,会执行函数中的代码,会将创建的对象赋值给构造函数中的this

    基本概念

    1. 通过同一个构造函数创建的对象都会关联一个神秘的对象,可以通过构造函数.prototype进行访问,这个神秘对象被称为原型对象
    2. 这个原型对象可以被用来做继承用,js中的继承有好几种,包括混入继承,经典继承,还有原型继承
    3. 通过构造函数创建出来的对象,不仅拥有构造函数中的属性,还拥有原型对象中创建出来的属性
    4. 实例化后的对象也可以通过__proto__进行访问原型对象,但是只是调试时使用,不推荐正式代码中使用

    继承方式

    原型基本

    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        //不仅拥有构造函数中的属性,还拥有原型中创建出来的属性
        Person.prototype.gender = 'male';
        var p = new Person('qx', 18);
        console.log(p.name);//qx
        console.log(p.age);//18
        console.log(p.gender);//male
    </script>

    混入继承

    <script>
        var o = {}
        var obj = {
            name: "张三",
            age: 18,
            sayHello: function () {
                console.log("Hello world");
            }
        }
        //混入式继承
        for (var k  in obj) {
            o[k] = obj[k];
        }
        console.log(o);
    </script>

    经典继承

    1、最早的原理

    <script>
        //通过替换原型,使得被创建出来对象也拥有传入对象的属性
        function jicheng(obj) {
            var o = {};
            o.__proto__ = obj;
            return o;
        }
    
        var o = jicheng({name: "张三"});
        console.log(o);
    </script>

    2、create方法

    <script>
        var o = {
            name: "周三"
        };
    
        var obj = Object.create(o);
        console.log(obj.name);
    </script>

    3、create方法存在兼容性问题

    <script>
        var obj = {
            name:"周三"
        };
    
        //检测浏览器的能力,如果没有Object.create方法就给他添加一个(不推荐使用)
        if(Object.create){
            var o = Object.create(obj);
        }else{
            Object.create = function () {
                function F() {
                }
                F.prototype = obj;
                var o = new F();
            }
            var o = Object.create(obj);
        }
    </script>
    <script>
        //自己定义个函数
        function create(obj) {
            if (Object.create) {
                return Object.create(obj);
            } else {
                function F() {
                }
    
                F.prototype = obj;
                return new F();
            }
        }
    </script>

    原型对象

    • 原型对象可以通过构造函数.prototype获得
    • 原型对象中的属性和方法,可以提供给那些通过此构造函数创建的对象使用,达到了全局使用的作用
    • 原型对象被替换,如果替换的属性与原有构造函数的属性相冲突,那么被创建的对象,依然首先访问的是构造函数中的属性
    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        Person.prototype.say=function () {
            console.log('chi');
        }
        var p=new Person('wx',18);
        p.say();//chi
    </script>
    • 原型对象添加属性和方法还可以通过以下方式,用{}来追加,{}这是字面量,同时代表了一个字面量对象,Person.prototype可以直接点出{}里面的属性和方法
    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        Person.prototype={
            say:function () {
                console.log('chi');
            },
            play:function () {
                console.log('wan');
            }
        };
        var p=new Person('wx',18);
        p.say();//chi
        p.play();//wan
    </script>
    • 原型对象在没有被替换前会输出一个constructor,其指向就是自己的构造函数,但是一旦被替换就会变成失去原本的构造函数,确实有,但是其指向的是Object的构造函数,最好最好的方式是在原型对象被替换之后,再次添加一个constructor属性,并且指向自己原先的构造函数
    • 补充下,为什么原型对象被替换后,其构造函数属性指向的是Object的构造函数?因为Person.protype,被称为原型对象,既然是对象,肯定也是通过某一个构造函数创建出来的,结果就是Object构造函数,所以Person.prototype有Object的构造函数

    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
    
        console.dir(Person.prototype);
        Person.prototype = {
            say: function () {
                console.log('chi');
            },
            play: function () {
                console.log('wan');
            }
        };
        Person.prototype.constructor = Person;
        console.dir(Person.prototype);
    </script>

    原型继承

    将animal这个对象赋值给Person.prototype,与上面的通过字面量{}道理是一样的,毕竟animal也是一个对象,里面的属性也是被{}包裹的

    <script>
        function Animal() {
            this.eat = 'chifan';
        }
    
        var animal = new Animal();
    
        function Person() {
            this.read = 'dushu';
        }
    
        var p1 = new Person();
    
        //person显示是具有animal的属性,毕竟人也是动物,具体方法是让person的原型被替换成animal
        //替换后,通过person构造函数创建出来的对象,不仅具有person的属性,还具有animal的属性
    
        Person.prototype = animal;//Person.prototype = new Animal()这样也行
    
        var p2 = new Person();
        console.log(p1);
        console.log(p2);
    </script>

    输出结果:

    1. p1与p2输出的都是Person类,所以都具有read属性
    2. p1与p2输出的原型__proto__中的constructor构造函数属性不一样,p1指向的是Person构造函数,p2指向的是Animal构造函数而且具有eat属性
    3. p1与p2输出的原型__proto__中的原型proto__指向都是一致的,都为Object类

    结果分析:

    1. p1与p2都是通过构造函数Person创建出来的,所以都具有read属性
    2. p1与p2创建的中间,执行了 Person.prototype= new Animal() 代码,所以p2的原型被替换了,导致p2的原型__proto__中指向的是Animal类,其构造函数当然也就是Animal,所以也具有eat属性
    3. p1与p2追踪到最后其实都是属于Object类,再追踪的话,Objec类的原型就是null
    4. 需要注意的是在执行 Person.prototype= new Animal() 代码之后,所有被创建出来Person对象的原型对象的构造函数constructor将不再指向原本的Person,而是Animal,所以最好将改过来,Person.prototype.constructor=Person

    复杂原型继承

    <script>
        function Yuanzi() {
            this.des = '进化';
        }
    
        function Animal() {
            this.skill = '捕猎';
        }
    
        function Human() {
            this.say = '说话';
        }
    
        Animal.prototype = new Yuanzi();
        Animal.prototype.constructor = Animal;
        Human.prototype = new Animal();
        Human.prototype.constructor = Human;
    
    
        var h = new Human();
        console.log(h);
    </script>

    打印对象h:

    1. h是一个Human类,原型对象指向Animal类,原型对象的构造函数又指向Human
    2. Animal类的原型对象指向的是Yuanzi,其构造函数又指向Animal
    3. h所以拥有了Human类,Animal类的所有属性

    构造器的作用

    • 构造器其实就是构造函数,实例化后的对象是可以通过对象.constructor的方式访问自己的构造函数的,这个属性其实是实例化后的对象本身没有,但是原型对象上有,那么它为什么被创建出来呢?
    • 其实很简单的,因为构造函数可以被看作是类,在简单类型中,var a=‘hello’ 或者 var b=1 他们的typeof类型都是可以找到的对应的内置对象(类),那么复杂类型呢?比如一个通过Person构造函数创建的对象通过typeof打印结果是object,通过Animal构造函数创建出来的对象通过typeof打印结果也是object,那么这些对象的类到底是什么呢?答案是constructor,有了它就可以让对象知道创建自己的构造函数是什么,等同于知道了自己属于什么类

    原型链基本概念

    1、每个构造函数都有原型对象

    2、每个对象都会有构造函数

    3、每个构造函数的原型都是一个对象

    4、那么这个原型对象也会有构造函数

    5、那么这个原型对象的构造函数也会有原型对象

    6、这样就会形成一个链式的结构,称为原型链

    7、通过修改原型链结构实现的继承,就叫做原型继承 

    8、三角关系到成立的必要性之一是 实例化对象p原型的原型与Object构造函数都是指向同一个对象(在内存中的内容与地址都是一样的)

    consolo.log (p.__proto__.__proto__===Object.prototype)//true

    属性搜索基本原则

    1. 当访问一个对象的成员的时候,会现在自身找有没有,如果找到直接使用,
    2. 如果没有找到,则去当前对象的原型对象中去查找,如果找到了直接使用
    3. 如果没有找到,继续找原型对象的原型对象,如果找到了,直接使用
    4. 如果没有找到,则继续向上查找,直到Object.prototype,如果还是没有,就报错

     原型对象的替换

    上面负责原型继承的模式是进行的原型的替换,这样虽然方便了,但是也是有问题的

    (1)给原型对象替换赋值,原型对象就会失去constructor属性

    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
    
        var p = new Person('qx', 18);
        console.log(Person.prototype);
        Person.prototype = {
            say: function () {
                console.log(this.name);
            }
        };
        console.log(Person.prototype);
    </script>

    (2)当然即使替换了原型对象,但是实例化后的对象依然具有constructor属性,而这个属性属于对象的原型对象的原型对象,从上图可以看出,以及下图也看出,替换前后打印的结果是不一样的。替换后,访问的是对象的原型对象的原型的构造函数

    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
    
        var p = new Person('qx', 18);
        console.log(Person.prototype.constructor);
        Person.prototype = {
            say: function () {
                console.log(this.name);
            }
        };
        console.log(Person.prototype.constructor);
    </script>

    (3)对原型对象进行替换,其实可以看作是将一个对象赋值给了一个普通对象,类似var a={},我们打印a,consolo.dir(a),结果是a并没有构造函数的属性

    (4)解决办法就是,替换了原型对象,必须在替换的对象上面加上constructor属性,并且赋值指向的是自身构造函数,和上面的复杂原型继承是一样的

    <script>
        function Person() {
    
        }
        Person.prototype = {
            constructor: Person;
        }
    </script>

    扩展内置对象

    • 确实可以通过修改内置对象的原型对象添加一些方法,但是直接添加是不安全的,安全的方式是通过一个中介,理由是内置对象是系统默认的,直接修改它是不安全的,让一个中介拥有内置对象的所有属性,再通过这个对象的原型对象添加方法是保险的
    <script>
        function MyArray() {
            // this.name = '我是一个数组';
        }
    
        MyArray.prototype = new Array();
        var arr1 = new MyArray();
        arr1.push(4)
        console.log(arr1);
        MyArray.prototype.say = function () {
            console.log('添加一个说的方法');
        }
        var arr2=new MyArray();
        arr2.say();
    </script>
  • 相关阅读:
    jquery 的 ajax 在 非阻塞 时返回 XMLHttpRequest
    关于TransactionScope出错:“与基础事务管理器的通信失败”的解决方法总结
    未能正确加载“radlangsvc.package,radlangsvc.vs,version=10.0.0,culture=neutra
    跨域iframe高度自适应(兼容IE/FF/OP/Chrome)
    Windows8不联网直接安装.Net 3.5 Framework的方法
    ubuntu创建、删除文件及文件夹,强制清空回收站方法
    Ubuntu java 环境变量
    mysql 和mssql2016中的json字段相关操作
    Windows任务计划
    配置mysql远程访问
  • 原文地址:https://www.cnblogs.com/wuqiuxue/p/8328286.html
Copyright © 2020-2023  润新知