原型和原型链

导读:

分类:面试总结

题目:原型和原型链

基础很重要,时刻给自己充电!

1.题目

  • 如何准确判断一个变量是数组
  • 写一个原型链继承的例子
  • 继承实现的其他方式
  • 描述new一个对象的过程
  • zepto及其他源码中如何使用原型链

2.知识点

2.1 构造函数

特点:以大写字母开头

function Foo(name,age){    //var obj = {}
   //this = {}
   this.name = name;    
this.age = age;    
this.class = 'class1'
   // return this}var f1 = new Foo('liming',19);

扩展

var o = {} 是 var o = new Object() 的语法糖

var a = [] 是 var a = new Array() 的语法糖

function Foo(){} 相当于 var Foo = new Function(){}

2.2 原型规则

五条规则:

1.所有引用类型(对象,数组,函数)都具有对象特性,即可以自由扩展属性

2.所有引用类型(对象,数组,函数)都具有一个__proto__(隐式原型)属性,是一个普通对象

3.所有的函数都具有prototype(显式原型)属性,也是一个普通对象

4.所有引用类型(对象,数组,函数)__proto__值指向它构造函数的prototype

5.当试图得到一个对象的属性时,如果变量本身没有这个属性,则会去他的__proto__中去找

for (var key in object) {    //高级浏览器中已经屏蔽了来自原型的属性
   //建议加上判断保证程序的健壮性
   if (object.hasOwnProperty(key)) {
       console.log(object[key]);
   }
}

2.3 this

this的几种使用场景

全局/函数

构造函数

对象

内部函数

call/apply

2.4 原型链

obj.__ proto __ . __ proto __ . __ proto __ ...

Object.prototype === null

instanceof 用于判断引用类型属于哪个构造函数

obj instanceob Foo

实际意义:判断 Foo.prototype 在不在 obj的原型链上

3.题目解答

3.1 如何准确判断一个变量是数组

arr instanceof Array

3.2 写一个原型链继承的例子

封装dom查询

function Elem(id){    
this.elem = document.getElementById(id);
};Elem.prototype.html = function(val){    
var elem = this.elem;    if (val) {
       elem.innerHTML = val;        
return this;
   }else{        
return elem.innerHTML;
   }
}Elem.prototype.on = function(type,fun){
   var elem = this.elem;
   elem.addEventListener(type,fun);
   return this;
}var div1 = new Elem('id1');
div1.html("test").on('click',function(){
   console.log('点击');
})

3.3 继承实现的其他方式

3.3.1 原型继承

        var obj = {            
        0:'a',            
        1:'b',            
        arr:[1]
       }        
       function Foo(arr2){            
        this.arr2 = [1]
       }       Foo.prototype = obj;        
        var foo1 = new Foo();        
        var foo2 = new Foo();       foo1.arr.push(2);
       foo1.arr2.push(2);        
        console.log(foo2.arr);  //[1,2]
       console.log(foo2.arr2); //[1]

优点:实现简单

缺点:

1.无法向父类构造函数传参

2.同时new两个对象时改变一个对象的原型中的引用类型的属性时,另一个对象的该属性也会修改。因为来自原型对象的引用属性是所有实例共享的。

3.3.2 构造继承

        function Super(b){            
        this.b = b;            
        this.fun = function(){}
       }        
        function Foo(a,b){            
        this.a = a;
           Super.call(this,b);
       }        
        var foo1 = new Foo(1,2);        
        console.log(foo1.b);

优点:可以向父类传参,子类不会共享父类的引用属性

缺点:无法实现函数复用,每个子类都有新的fun,太多了就会影响性能,不能继承父类的原型对象。

3.3.3 组合继承

function Super(){    // 只在此处声明基本属性和引用属性
   this.val = 1;    
this.arr = [1];
}//  在此处声明函数Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...function Sub(){
   Super.call(this);   // 核心
   // ...}
Sub.prototype = new Super();    

优点:不存在引用属性共享问题,可传参,函数可复用

缺点:父类的属性会被实例化两次,获取不到真正实例父类(无法区分实例是父类创建还是父类创建的)

优化:

         function Super(b){            
         this.b = b;            
         this.fun = function(){}
       }       Super.prototype.c = function(){
         console.log(1111)}        
         function Foo(a,b){            
         this.a = a;
           Super.call(this,b);
       }       Foo.prototype = Super.prototype;        //修复构造函数:
       var foo1 = new Foo(1,2);

缺点:无法区分实例是父类创建还是子类创建的

3.3.4 寄生组合继承

         function Super(b){            
         this.b = b;
       }       Super.prototype.c = function(){console.log(1111)}        
         function Foo(a,b){            
         this.a = a;
           Super.call(this,b);
       }        var f = new Function();
       f.prototype = Super.prototype;
       Foo.prototype = new f();        
         //等同于 Foo.prototype = Object.create(Super.prototype)
       var foo1 = new Foo(1,2);

对父类的prototype进行一次寄生,即包装成一个空对象的prototype,再把这个对象实例化出来作为子类的peototype。

缺点:无法区分实例是父类创建还是子类创建的

可以添加以下代码:

Foo.prototype.constructor = Foo

这种解决方法不能用于上面的组合优化方法,因为子类父类引用的是同一个原型对象,修改会同时修改。

总结:

继承主要是实现子类对父类方法,属性的复用。

来自原型对象的引用属性是所有实例共享的,所以我们要避免从原型中继承属性。

在构造函数中通过call函数可以继承父类构造函数的属性和方法,但是通过这种方式实例化出来的实例会将父类方法多次存储,影响性能。

通过组合继承我们使用call继承属性,使用原型继承方法,可以解决以上两个问题,但是通过这种方式实例化出来的对象会存储两份父类构造函数中的属性。

用父类的原型构造一个新对象作为子类的原型,就解决了多次存储的问题,所以最终的寄生组合继承就是最佳继承方式,它的缺点就是书写起来比较麻烦。

3.3.6 node源码中的继承实现

function inherits(ctor, superCtor) {
 ctor.super_ = superCtor;
 ctor.prototype = Object.create(superCtor.prototype, {    
constructor: {      
value: ctor,      
enumerable: false,      
writable: true,     
 configurable: true
   }
 });
};function Stream(){    //...}function OutgoingMessage() {
 Stream.call(this);  //...}inherits(OutgoingMessage, Stream);OutgoingMessage.prototype.setTimeout = ...

以上是寄生组合继承的一个实例。

1.在OutgoingMessage构造函数中通过call继承Stream构造中的属性。

2.调用inherits方法继承Stream原型中的属性。

3.扩展OutgoingMessage自身原型的函数。

inherits方法中使用了Object.create方法,该方法的作用是通过指定的原型对象和属性创建一个新的对象。

ctor.prototype=Object.create(superCtor.prototype,{.....});

该方法实际上就做了我们上面寄生组合继承中的工作

var f = new Function();
f.prototype =superCtor.prototype;return new f();

后面的参数是给原型对象添加属性,可选属性(非必填),即把自身作为新创建对象的构造函数。

value: 表示constructor 的属性值;
writable: 表示constructor 的属性值是否可写;[默认为: false]
enumerable: 表示属性constructor 是否可以被枚举;[默认为: false]
configurable: 表示属性constructor 是否可以被配置,例如 对obj.a做 delete操作是否允许;[默认为: false]

3.4 描述new一个对象的过程

  1. 创建一个对象
  2. {}.proto = 构造函数.prototype
  3. this指向这个对象
  4. 执行代码即对this赋值
  5. 返回this

3.5 zepto及其他源码中如何使用原型链

var Zepto = (function(){    
var $,zepto = {}    
   // ...省略N行代码...
   
   $ = function(selector, context){        
return zepto.init(selector, context)
   }   zepto.init = function(selector, context) {
        var dom        
       // 针对参数情况,分别对dom赋值
       
       // 最终调用 zepto.Z 返回的数据
       return zepto.Z(dom, selector)
   }      fnction Z(dom, selector) {
              var i, len = dom ? dom.length : 0
    for (i = 0; i < len; i++) 
              this[i] = dom[i]      
              this.length = len      
              this.selector = selector || ''
   }  zepto.Z = function(dom, selector) {     
              return new Z(dom, selector)
  }
 
   $.fn = {        
              // 里面有若干个工具函数
   }
     
 
   zepto.Z.prototype = Z.prototype = $.fn  
   
   // ...省略N行代码...
   
   return $
})()window.Zepto = Zeptowindow.$ === undefined && (window.$ = Zepto)

本文分享自微信公众号 - code秘密花园(code_mmhy),作者:ConardLi

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-12-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 前端路由原理解析和实现

    路由的概念来源于服务端,在服务端中路由描述的是 URL 与处理函数之间的映射关系。

    ConardLi
  • es6类和继承的实现原理

    javascript使用的是原型式继承,我们可以通过原型的特性实现类的继承, es6为我们提供了像面向对象继承一样的语法糖。

    ConardLi
  • Node进阶——之事无巨细手写Koa源码

    对比发现,相对原生,Koa多了两个实例上的use、listen方法,和use回调中的ctx、next两个参数。这四个不同,几乎就是Koa的全部了,也是这四个不同...

    ConardLi
  • JavaScript学习总结(五)

    之前的几讲中我们曾经说过,JavaScript中是没有类的概念的。但是我们讲过对象,那么这个对象是怎么来的呢? 只要有函数即可创建对象

    roobtyan
  • this_原型链_继承

    小胖
  • 第201天:js---实现继承的5种方式

    半指温柔乐
  • js设计模式补白之 this/call和apply

    这里call(this)显然是把当前的作用域(window)绑定给了getName方法。

    一粒小麦
  • javascript中常用的设计模式,教你写出更好的前端代码

    今天给大家介绍js中常用的设计模式,也让自己对js设计模式有一个更清晰的认识,下面我们直接进入今日的主题

    前端老鸟
  • difference between view exit and view destroy

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    Jerry Wang
  • Koa-router源码解读

    链式调用 在 koa 中,对中间件的使用是支持链接调用的。同样, 对于多个路径的请求,koa-router 也支持链式调用: router .get(‘/‘...

    xiangzhihong

扫码关注云+社区

领取腾讯云代金券