20

本篇主要是记录一下对js中对于原型的理解...

原型

原型涉及到构造函数, 原型对象, 实例化对象三者之间的关系...

  • 构造函数

       function Person (name,age) {
           //(1)创建一个空对象: {}
           //(2)将this指向这个空对象 : this = {}
           //(3)执行构造函数赋值代码(完成对象的赋值)
           this.name = name;
           this.age = age;
           this.sayHi = function () {
               console.log(this.name + 'hello world');
           };
           //(4)返回这个对象
       };
       var man = new Person('huahua',18); 
         
       1.何为构造函数?
           构造函数:首先,它是函数,并且任何的函数都可以作为构造函数存在,它的本质是初始化对象。
           构造函数都是和new关键词一起使用的。 new就是在创建对象,从声明开始一共做了4件事(如上),构造函数就是在为初始化的对象添加属性和方法(成员)
    
       2.构造函数的特点:
           a:构造函数的首字母必须大写,用来区分于普通函数
           b:内部使用的this对象,来指向即将要生成的实例对象
           c:使用New来生成实例对象
    
  • 实例对象

       1.上面的 man 就是通过Person这个构造函数实例化出来一个对象,我们称为 **实例化对象**;何为对象的实例化呢? 
    
       2.在我看来就是给一个空对象添加了一些属性和方法,使其具有了一些特征和行为...也就是上面new关键字干的事;跟面向对象中的一些概念比较类似...
       面向对象编程: 面向对象就是对现实中的事物进行抽象化...然后再给其设置特征属性和行为使之具体化; 面向对象就是对面向过程进行封装后的结果...
    
       3.实例对象中存在一个__proto__属性; 这个属性指向了构造函数的原型prototype...  
       注意: 实例对象访问成员的规则:先看自己有没有这个成员,如果有则访问,没有则访问原型的  
    
  • 原型对象

       上面已经聊过构造函数和实例化对象了,那么原型对象又是什么呢?
           当我们在声明一个函数时, 系统会帮我们创建一个与该函数对应的属性prototype,我们称它为原型对象;
           以上面的Person为例,这个prototype是该函数的一个属性,我们可以调用Person.prototype来修改其成员或者进行重写;
    
           原型对象中有一个构造器指针constructor属性来指向对应的构造函数,他的作用是可以让实例对象知道自己是哪一个构造函数生成的;
           如 man.constructor 即man.__proto__.constructor指向了 Person. 
    
  • 下面用一张图来表示他们之间的关系...

    原型对象中可以存储很多成员属性和方法,多个实例对象之间就能共享这些属性和方法; 类似实现了面向对象中 继承 的效果...

    面向对象的三大特性:

       
       封装:将功能代码封装到对象中,只暴露外部接口(API),使用者无需关心内部实现
       继承:一个对象拥有另一个对象所有的成员变量(属性和方法)
       多态: 一个对象在不同情况下的多种状态; 一个对象经过不同操作后会有不同的行为....
            (js从语法的角度上来说没有多态,因为js是基于对象的语言)
    
       js实现继承的方式:
           1. 我们可以遍历父对象,将父对象的属性动态添加到子对象中 (适用于一个子对象的继承)
               for (var key in father){
                   son[key]  = father[key];
                };
           2. 替换原型:将父对象作为子对象构造函数的原型(但是会丢失之前的原型对象的成员)
                // 子对象用构造函数来实例化
                function Son(name, age) {
                    this.name = name;
                    this,age = age;
                }
                Son.prototype.father = {
                    parent: 'laosong',
                    age: 47,
                } // Son 原型对象中的成员
                var son = new Son('xiaowang', 24)
                var father = {
                    name: 'laowang',
                    age: 48,
                }
                Son.prototype = father; // 相当于Son的原型被重新赋值,替换了,laosong不在了
    
           3. 综合上面两种情况: 将父对象的成员动态添加到子对象的原型中, 这样就不会丢失了
               for (var key in father){
                   Son.prototype[key]  = father[key];
                };
    
               /**混合式继承封装
               @param method:子对象的构造函数
                @param father:要继承的父对象
                */
               function extendMehtd ( method,father ) {
                   for (var key in father){
                       method.prototype[key] = father[key];
                   }
               };
    
           4. 构造函数实现继承
               // 通过更改this的指向来实现
               function Person(name, age) {
                   this.name = name || 'hello';
                   this.age = age || 200;
               };
               function Stu(sex, name, age) {
                   this.sex = sex;
                   // 调用Person构造函数,修改Person中的this指向为当前Student这个构造函数中new创建的对象
                   // 继承Person中默认的初始化属性
                   Person.call(this, name, age);
               };
    
               var s = new Stu('male');
               console.log(s); // age: 200,name: "hello",sex: "male"
    

原型链

js中, 每一个实例对象都存在一个__proto__属性指向了自己的原型prototype; 但是原型本身也是一个对象,
也有自己的__proto__属性,指向自己的原型,以此类推就形成一个链式结构,称之为原型链...

对象访问原型链中成员规则:就近原则
先看对象自己有没有,有则访问,没有则看原型有没有,有则访问,没有则看原型的原型有没有,以此类推...直到原型链的终点(null);
如果还没有 : 如果是访问属性:则返回undefined 如果访问的是方法:则会报错 xxxx is not a function

  • 图解原型链

    以数组对象为例:

  • DOM中的原型链

图解完整原型链

JS中原型链完整图

以上便是JS中完整的原型链图解了...

关于对象的一些知识点补充

1.静态成员和实例成员
    静态成员: 函数对象持有的成员(属性,方法)
    实例成员: 构造函数实例化出来的对象持有的成员

2.instanceof 关键字
    语法: 对象 instanceof 构造函数
    作用: 用来检测右边函数的原型 是否 在左边对象的原型链中(true/false)
    如: Object instanceof Object // true

3.Object.prototype(对象原型)
    --所有对象的原型链中都会指向它;所以所有的对象都可以访问Object.prototype原型中的成员

    常用几个成员:
        1.hasOwnProperty(): 检查对象是否包含某个成员;
            条件: 自己的成员
        2.isPrototypeOf(): 检查(左边)一个对象是不是(右边)另一个对象的原型

        3.propertyIsEnumerable(): 检查对象是否可以枚举某个属性
            条件: (1)是自己的成员  (2)可以被for-in循环遍历 (自己的和原型的)    

4.Function.prototype(函数对象Function的原型)
    --所有的函数对象原型都会指向Function构造函数的原型,所有的函数对象都可以访问Function.prototype中的成员

    常用的一些成员:
        1. name:获取函数名 (比较鸡肋)

        2.caller:获取调用本函数的引用;通过console.log(fn.caller)可以知道自己在哪个地方被人调用(全局调用函数,这里的caller指向null)

        3.length:获取函数形参的数量; fun.length 可以知道函数设置的形参个数

        4.arguments:获取函数所有的实参; 
            可以理解为函数内部一个隐藏的形参,作用是获取函数所有的实参,与形参一一对应...

            arguments对象的两个常用属性:
                1.callee:指向函数自身, 应用于匿名函数的递归调用...
                  arguments.callee === fn //true
                
                2. length: 实参的个数
                  arguments是一个伪数组...

        5.给内置的构造函数原型添加自定义成员
            当内置构造函数自带的方法不够用,无法实现需求时,我们就需要给添加自定义方法;直接添加可能会出现多个人员操作出现相同的方法名,导致被覆盖掉了
            所以需要采用安全的方法添加来避免覆盖...

            使用替换原型继承(自定义构造函数,将原型指向内置对象)
                // 通过构造函数的方式来添加;
                function NewArr(name) {
                    this.name = name;
                };
                
                NewArr.prototype = []; // 修改为一个空数组对象;此时NewArr的原型拥有数组对象所有的方法
                NewArr.prototype.hello = {
                    name: 'hello world',
                };
                NewArr.prototype.min = function () {
                    var min = Infinity;
                    for(var i=0; i< this.length; i++) {
                        if (this[i] < min) {
                            min = this[i];
                        }
                    };
                    return min;
                };

                // 创建一个新对象
                var arr1 = new NewArr('huhua');
                var arr3 = new NewArr();
                arr3.push(1,2,3,-1);

                console.log(arr3.min());
                console.log(arr1);
                console.log(arr1.__proto__);
                console.log(NewArr);
                console.log(NewArr.prototype);
                console.log(NewArr.__proto__.constructor);

                var arr2 = [12,241,21];
                console.log(arr2.min());  // 不能访问

                Array.prototype:对象类型赋值的时候拷贝的是地址,修改了NewArr的原型之后,Array.prototype也会修改
                []:  由于空数组的原型会指向Array.prototype,根据原型链中成员访问规则,NewArr实例对象可以访问数组成员的成员
                      并且,修改MyArr的原型对象,本质上是修改这个空数组,不会对Array.protpotype造成影响

a_dodo
2.4k 声望1k 粉丝

天下事有难易乎?