原型与原型链

144 阅读2分钟

1、原型

JavaScript中,万物皆对象。

一般分为Object(普通对象)和Function(函数对象)。其中,通过new Function 产生的对象是函数对象,其他对象都是普通对象。

每次声明函数时,都会产生一个原型属性,即prototype。这个属性是一个指针,指向一个对象,这个对象包含可以由特定对象实例共享的一些属性和方法,即原型对象。原型对象上包含constructor属性,这个属性指向构造函数本身。简单来说就是,每个函数都有一个原型属性(prototype),原型上的指针指向一个对象,对象包含所有实例共享的属性和方法。

所以,原型定义了一些公用的属性和方法,利用原型创建出来的新对象实例会共享原型的所有属性和方法。一般通过new + 构造函数 来创建新对象实例。例如:

var a = new A();

新对象实例a被构造函数A创建出来,新对象a没有prototype属性,只有__proto__属性,prototype属性只有构造函数A才有。 构造函数A中有prototype属性,指向a的__proto__属性指向的地方,即构造函数A的原型对象。所以实例a可以访问构造函数A的原型对象上的所有属性和方法。

所以,原型是一个对象,其他对象可以通过原型实现属性继承。原型的构成:原型的属性、方法和constructor。

2、原型链

JS中万物皆对象。每个被构造出来的新对象实例都有一个__ proto__属性,指向创建该对象的构造函数的prototype属性所指向的地方,即构造函数的原型对象。当访问一个对象的某个属性时,会先查找对象本身,如果本身没有时,就会沿着对象的__proto__去寻找,即在构造函数的原型对象上寻找,找到就返回其值,没找到就继续沿着对象的__proto__寻找,直到寻找到null,返回undefined就结束。这就形成了一条链式结构,即原型链。原型链实现了属性和方法的继承。

当查找对象的属性时,会先检查对象本身是否存在该属性,如果不存在,会经由__ptoto__在原型链上一层层访问,而不会访问自身的ptototype。例如:


<script src="">
        function Person(name, sex) {
            this.name = name;
            this.sex = sex;
        }
        var person1 = new Person('charon', 23);       
    </script>

新对象person1被构造函数Person创建出来,即person1.ptoto == Person.prototype,Person函数又是被Object函数创建出来,即Person.proto == Object.prototype,而Object的原型对象上是null,即Object.prototype==null。

所以,person1的原型链是:

person1.proto == Person.prototype

person1.proto.proto == Object.prototype

person1.proto.proto.proto == null

person1的原型链如下图所示:

person1原型链.png

3、原型链练习题1分析


 <script >
        // 重点 改变this指向
        // 题1 这个是在构造函数创建的新对象里新增talk/mycode属性
        function Person(name, age) {//this指向Itgril创建的新对象
            // console.log(this);// Itgirl {}
            this.name = name;
            this.age = age;
            this.talk = function () {
                console.log(this.name + '的年龄是' + this.age + '岁');
            }
            // console.log(this);// Itgirl {name: '一个女生', age: 20, talk: ƒ}
        }

        // Itgirl.prototype = new Person;  不影响
        function Itgirl(name, code, age) {
            // console.log(this);// Itgirl {}// this指向Itgril创建的新对象
            Person.call(this, name, age);//改变的是Person函数里面的this的指向
            // console.log(this);// Itgirl {name: '一个女生', age: 20, talk: ƒ} this指向Person创建的新对象
            this.code = code;
            this.mycode = function () { console.log('我在学习' + this.code); }
            // console.log(this);// tgirl {name: '一个女生', age: 20, code: 'H5', talk: ƒ, mycode: ƒ}
        }

        var girl1 = new Itgirl('一个女生', 'H5', 20);
        console.log(girl1);// Itgirl {name: '一个女生', age: 20, code: 'H5', talk: ƒ, mycode: ƒ}
        // console.log((girl1.__proto__===Itgirl.prototype));// true
        // console.log((girl1.__proto__.__proto__===Person.prototype));// false
        // console.log((girl1.__proto__.talk===Itgirl.prototype.talk));// true
        girl1.talk();// 一个女生的年龄是20岁
        /*
        1.girl1对象有talk属性 gril1对象可以直接访问talk属性
        2.this.talk = function(){console.log(this.name + '的年龄是' + this.age + '岁');}
        3.返回结果 一个女生的年龄是20岁
        */ 
        girl1.mycode();//我在学习H5
        /*
        1.girl1对象没有mycode属性 沿着原型链去找
        2.this.mycode = function(){console.log('我在学习' + this.code);}
        3.返回结果 我在学习H5
        */
 
        /*题1 分析
        window{
            girl1:undefined  0x001
            Person:f...
            Itgirl:f...
        }
        Itgirl-AO{
            name:undefined  '一个女生'
            code:undefined  'H5'
            age:undefined   20
        }
        Person-AO{
            name:undefined '一个女生'
            age:undefined   20
        }
        虚拟内存 
        栈                        堆
        Itgirl-AO 0x001         0x001:{name: '一个女生', age: 20, talk: ƒ}
                                   -->{name: '一个女生', age: 20, talk: ƒ , code:'H5',mycode:'我在学习H5'}
        Person-AO 0x001         
        girl1     0x001

        原型链
        gril1         Itgirl.prototype       Object.prototype    null
        __proto__
                      __proto__              __proto__
        */
    </script>
    

4、原型链练习题2分析

<script src>
        // 重点 原型链的继承
        // 题2 这个是在构造函数的原型对象上新增sayName/sayAge属性
        function SuperType(name) {// this指向SubType创建的新对象 
            // console.log(this);// SuperType {}
            // 给新对象SubType-AO1添加 name colrs属性
            this.name = name;
            this.colors = ["red", "blue", "green"];
            console.log(this);// 第一次调用 SubType {name: 'Nicholas', colors: Array(3)} 第二次调用SubType {name: 'Greg', colors: Array(3)}

        }
        // 在SuperType的原型对象(SuperType.prototype)上新增sayName属性 值为函数体
        SuperType.prototype.sayName = function () {
            // console.log(this);// SubType {name: 'Nicholas', colors: Array(4), age: 29}
            console.log(this.name);
        };
        // console.log(SuperType.prototype);// {sayName: ƒ, constructor: ƒ}

        function SubType(name, age) {
            // console.log(this);// SubType {}
            SuperType.call(this, name);// SuperType构造函数里面的this指向被改变为SubType构造函数创建的新对象
            // 给新对象SubType-AO1添加 age属性
            this.age = age;
            // console.log(this);// SubType {name: 'Nicholas', colors: Array(3), age: 29}
        }
        SubType.prototype = new SuperType();

        // 在SubType的原型对象(SubType.prototype)上新增sayAge属性 值为函数体
        SubType.prototype.sayAge = function () {
            // console.log(this);// SubType {name: 'Nicholas', colors: Array(4), age: 29}
            console.log(this.age);
        };

        var instance1 = new SubType("Nicholas", 29);
        instance1.colors.push("black");
        // console.log(instance1);// SubType {name: 'Nicholas', colors: Array(4), age: 29}
        console.log(instance1.colors);// ["red", "blue", "green","black"]

        // 调用sayName() instance1.__proto__.sayName()
        console.log(instance1.__proto__.sayName === SuperType.prototype.sayName); // true
        instance1.sayName();// 'Nicholas'
        /* 1.instance1对象身上有sayName属性 沿着原型链去找
           2.在instance1.__proto__上找 找到sayName属性 (instance1.__proto__ === SubType.prototype)
           3.function(){console.log(this.name);}
           4.打印结果 'Nicholas'
        */


        // 调用sayAge() instance1.__proto__.__proro__.sayAge()
        instance1.sayAge();// 29
        /* 1.instance1对象身上没有sayAge属性 沿着原型链去找
           2.在instance1.__proto__上找 没有sayName属性 (instance1.__proto__ === SubType.prototype)
           3.在instance1.__proto__.__proto__上找 找到sayName属性 (instance1.__proto__.__proto__ === SuperType.prototype)
           4.function(){console.log(this.age);}
           5.打印结果 29
        */

        var instance2 = new SubType("Greg", 27);
        console.log(instance2.colors);// ["red", "blue", "green"]
        // 调用sayName() instance2.__proto__.sayName
        instance2.sayName();// 'Greg'
        /* 1.instance1对象身上没有sayName属性 沿着原型链去找
           2.在instance1.__proto__上找 找到sayName属性 (instance1.__proto__ === SubType.prototype)
           3.function(){console.log(this.name);}
           4.打印结果 'Greg'
        */
        // 调用sayAge() instance2.__proto__.__proro__.sayAge
        instance2.sayAge();// 27
        /* 1.instance1对象有sayAge属性 
           2.在instance1.__proto__上找 没有sayName属性 (instance1.__proto__ === SubType.prototype)
           3.在instance1.__proto__.__proto__上找 找到sayName属性 (instance1.__proto__.__proto__ === SuperType.prototype)
           4.function(){console.log(this.age);}
           5.打印结果 27
        */
        console.log(instance2);// SubType {name: 'Greg', colors: Array(3), age: 27}


        /*
        window{
            instance1:undefined  0x001
            instance2:undefined  0x002
            SuperType:f...
            SubType:f...

        }
        SuperType-AO1{
            name:undefined   'Nicholas'
        }
        SubType-AO1{
            name:undefined 'Nicholas'
            age:undefined   29

        }
        SuperType-AO2{
            name:undefined   'Greg'
        }
        SubType-AO2{
            name:undefined 'Greg'
            age:undefined   27

        }
        虚拟内存
        栈                       堆
        SubType-AO1   0x001     0x001:SuperType添加{name: 'Nicholas', colors: Array(3)} 
                                  --> SubType添加  {name:'Nicholas',color:["red", "blue", "green","black"],age:29}
        SuperType-AO1 0x001
        instance1     0x001

        SubType-AO2   0x002     0x002:SuperType添加{name:'Greg',color:["red", "blue", "green"]}  
                                  --> SubType添加  {name:'Greg',color:["red", "blue", "green",age:27}
        SuperType-AO2 0x002
        insstance2    0x002

        原型链
        instance1          SubType.prototype         SuperType.prototype    null
        __proto__
                           __proto__                  __proto__
                           sayAge(29)              sayName('Nicholas')
        
        instance2          SubType.prototype         SuperType.prototype    null
        __proto__
                           __proto__                  __proto__
                           sayAge(27)              sayName('Greg')
        */
    </script>

5、原型链练习题3分析

    <script>
        // 只要有new关键字 函数一定会运行(不需要再加小括号表调用)
        // 函数体中的this 指向距离this最近的function的调用者
        /*
        window:{
            getName:undefined --> f(5) --> f(4)
            Foo:f..

        }
        Foo-AO:{
            getName:f(2)

        }
        Foo.prototype-AO:{
            getName:f(3)
        }
        */
        // 第2步 提升Foo 值为函数体
        function Foo() {
            getName = function () {// 全局的getName属性
                console.log(1);
            }
            return this;
        }
        // 第4步 给Foo新增属性 值为函数体f(2)
        Foo.getName = function () {
            console.log(2);
        }
        // 第5步 给Foo.prototype新增属性 值为函数体f(3)
        Foo.prototype.getName = function () {
            console.log(3);
        }
        // 第1步   提升var声明的变量getName 值为undefined
        // 第6步   同名变量 getName 覆盖函数体f(5)  重新赋值为函数体f(4)
        var getName = function () {
            console.log(4);
        }
        // 第3步 提升getName函数 因为与var声明的变量名为同名变量 所以覆盖值undefined 重新赋值为函数体f(5) 接下来执行非var 非function的部分
        function getName() {
            console.log(5);
        }
        Foo.getName();// 2
        /*
        7.Foo没有调用 直接访问getName属性 是Foo在调用getName
        8.调用(Foo.getName) 执行f(){console.log(2);}得到结果 2
        */
        getName();// 4
        /*9.window.getName() 即访问全局中的getName属性 f(){console.log(4);}// 4     */
        Foo().getName();// 1
        /*
        10.Foo发生调用 函数体执行
        11.执行f(){getName=function(){console.log(1);},return this} 
        12.第一步 同名变量 getName 覆盖值f(4) 重新赋值f(1) 第二步 return this 返回结果 return window
        13.Foo().getName()==window.getName(); 执行函数体f(1) // 1
        */
        getName();// 1
        /*14.window.getName() 访问全局范围内的getName属性*/
        new Foo.getName();// 2
        /*
        15.new Foo.getName()==new (Foo.getName)() 整体成为一个new带参 构造一个新对象
        16.{}.__proto__===Foo.getName.prototype
        17.{getName:f(){console.log(2)}}
        
        {}            Foo.getName.prototype      Object.prototype  null
        __proto__   
                       __proto__                  __proto__   
                      
        */
        new Foo().getName();// 3
        /*
        18.new Foo().getName()===(new Foo()).getName() 先整体成为new带参 构造一个新对象 
        19.(new Foo()).getName===(Foo.prototype).getName()
        20.{}.__proto__===Foo.prototype  
        21.{getName:f(){console.log(3)}}新对象身上有getName属性
        
        {}            Foo.prototype      Object.prototype  null
        __proto__
                       __proto__            __proto__   
        */
        new new Foo().getName();// 3 只要有new关键字 函数一定会运行
        /*
        22.new new Foo().getName()==new (new Foo()).getName()==new ((new Foo()).getName)() == new Foo.prototype.getName()
        23.{}.__proto__===Foo.prototype.getName.prototype
        24.{getName:f(){console.log(3)}}
        
        {}            Foo.prototype.getName.prototype      Object.prototype  null
        __proto__
                       __proto__                             __proto__   
        */
    </script>