前端面试回顾(1)---javascript的面向对象

前言

前一阵面试,过程当中发现问到一些很基础的问题时候,本身并不能很流畅的回答出来。或者遇到一些基础知识的应用,因为对这些点理解的不是很深刻,拿着笔竟然什么都写不出来,因而有了回顾一下这些基础知识的想法。javascript

首先就是面试中常常会问到的,JS是怎么实现继承的,其实问到继承,面试官想问的可能仍是你对JS面向对象的理解吧。java

这一部分的主要参考资料:《JavaScript高级程序设计》、《JavaScript设计模式》
若是有什么错误的地方,也但愿看到这篇文章的小伙伴给我指出来,谢谢 ^_^面试

1、对象

1.1建立对象

Javascript是一种基于对象(object-based)的语言,你遇到的全部东西几乎都是对象。
一个简单的对象建立:编程

var People = {
    name : "eavan",
    age : 24,
    getName : function(){
        alert(this.name);        //eavan
    }
}

使用的时候就能够用People.name,获取People这个对象的name属性,或者是People.getName()来获得People的name值。
另外一种对象建立方式:设计模式

var People = new Object();
People.name = "eavan";
People.age = 24;
People.getName = function(){
    alert(this.name);
}

这里用到了new,就顺便提一下在使用new的时候发生了什么,其实在使用new的时候,大体能够认为作了这三件事,看下面的代码:编程语言

var People  = {};                      //咱们建立了一个空对象People
People.__proto__ = Object.prototype;   //咱们将这个空对象的__proto__成员指向了Object函数对象prototype成员对象
Object.call(People);         //咱们将Object函数对象的this指针替换成People,而后再调用Object函数

1.2封装

简单来讲就是对一些属性的隐藏域暴露,好比私有属性、私有方法、共有属性、共有方法、保护方法等等。而js也能实现私有属性、私有方法、共有属性、共有方法等等这些特性。函数

像java这样的面向对象的编程语言通常会有一个类的概念,从而实现封装。而javascript中没有类的概念,JS中实现封装主要仍是靠函数。this

首先声明一个函数保存在一个变量里面。而后在这个函数(类)的内部经过对this变量添加属性或者方法来实现对类添加属相或者方法。prototype

var Person = function(){
    var name = "eavan";             //私有属性
    function checkName(){};         //私有方法

    this.myName = "gaof";            //对象共有属性
    this.myFriends = ["aa","bb","cc"];
    this.copy = function(){}         //对象共有方法

    this.getName = function(){       //构造器方法
        return name;
    };            
}

纯构造函数封装数据的问题是:对像this.copy = function(){}这种方法的建立,其实在执行的时候大可没必要绑定到特定的对象上去,将其定义到全局变量上也是同样的,并且其过程至关于实例化了一个Function,也大可没必要实例化这么多其实干同一件事的方法。而这个小问题的解决能够用原型模式来解决。设计

1.3理解原型

在每建立一个函数的时候,都会生成一个prototype属性,这个属性指向函数的原型对象。而其是用来包含特定类型的全部实例共享的属性和方法。因此,直接添加在原型中的实例和方法,就会被全部实例所共享。

一样仍是上面的Person的例子,咱们能够为其原型添加新的属性和方法。

Person.isChinese = true;                          //类的静态共有属性(对象不能访问)
Person.prototype.sex = "man" ;            //类的共有属性
Person.prototype.frends = ["gao","li","du"];
Person.prototype.isBoy = function(){};    //类的共有方法

原型封装数据的问题:对绑定在prototype上的引用类型的变量,因为被全部对象所共有,其中某一个对象对该数据进行修改,当别的对象访问该数据的时候,所访问到的值就是被修改后的。
好比以下代码:

var person1 = new Person();
person1.frends.push("dd");
console.log(person1.frends);    //["gao", "li", "du", "dd"]
var person2 = new Person();
person2.frends.push("ee");
console.log(person2.frends);     //["gao", "li", "du", "dd", "ee"]

本来但愿对person1和person2的friends属性分别添加新的内容,结果两者的friends属性竟然是“公用”的!

综上,最多见的方式应该是组合使用构造函数和原型模式,构造函数用于定义实例属性,原型模式用于定义方法和共享的属性。

每一个类有三部分构成:第一部分是构造函数内,供实例对象化复制用。第二部分是构造函数外,直接经过点语法添加,供类使用,实例化对象访问不到。第三部分是类的原型中,实例化对象能够经过其原型链间接访问到,也是为全部实例化对象所共用。

在说到对象实例的属性的时候,咱们有一个问题,就是在访问一个属性的时候,这个属性是属于实例,仍是属于这个实例的原型的呢?

好比仍是上面的例子,咱们为person2实例增长一个sex属性,这时候访问person2的sex属性时,获得的是咱们增长的值。说明为对象实例添加一个属性的时候,这个属性就会屏蔽原型对象中保存的同名属性。

person2.sex = "woman";
    console.log(person1.sex);                //man
    console.log(person2.sex);                //woman

这个时候咱们可使用hasOwnProperty()方法来检测一个属性是存在于实例中,仍是存在于原型中。若是实例中有这个属性,hasOwnProperty()会返回true,而hasOwnProperty()并不会感知到原型中的属性。因此能够用这个方法检测属性究竟是存在于实例中仍是原型中。

console.log(person1.hasOwnProperty("sex"));        //原型中的属性,返回false
console.log(person2.hasOwnProperty("sex"));        //实例中的属性,返回true

2、继承

ECMAScript中描述了原型链的概念,并将原型链做为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另外一个引用类型的属性和方法。

2.1 原型链继承

以下代码:

function Super(){
    this.val = true;
    this.arr = ["a"];
}
function Sub(){
        //...
}
Sub.prototype = new Super();

var sub = new Sub();
console.log(sub.val)        //true

以上代码定义了Super和Sub两个类型,继承的核心就一句话:Sub.prototype = new Super() 将父类的一个实例赋给子类的原型。这样子类就可以使用父类实例所拥有的方法和父类原型中的方法。

这种状况要想给子类添加本身的方法或者是覆盖父类中某个方法的时候,必定要在放在替换原型语句后面。不然写在原型上的方法都会丢失。

并且在给子类添加新方法的时候,不能使用字面量的方式添加新方法,这样会致使继承无效。
如:

Sub.prototype = new Super();
Sub.prototype = {                        //错误的方式
    getVal : function(){
        //...
    }
}

以上代码刚刚把Super的实例赋给原型,紧接着又将原信替换成一个对象字面量,致使如今原型包含的是一个Object的实例,并不是Super的实例,所以原型链被切断了,Sub和Super已经没有关系了。

原型链的问题:
最主要的问题有两个:一是因为引用类型的原型属性会被全部实例所共享,因此经过原型链继承时,原型变成了另外一个类型的实例,原先的实例属性也就变成了如今的原型属性,以下代码:

function Super(){
    this.friends = ["peng","gao"];
}
function Sub(){
        //...
}
Sub.prototype = new Super();
var sub1 = new Sub();
var sub2 = new Sub();
sub1.friends.push("du");
console.log(sub2.friends);            //["peng", "gao", "du"]

这个例子说明的就是上面的问题,子类的全部实例共享了父类中的引用类型属性。

原型链继承的另外一个问题是在建立子类行的实例的时候,无法向父类的构造函数传递参数。

2.2 构造函数继承

具体实现:

function Super(){
    this.val = true;
    this.arr = ["a"];
}
function Sub(){
       Super.call(this);
}
var sub = new Sub();
console.log(sub.val)        //true

这种模式这是解决了原型链继承中出现的两个问题,它能够传递参数,也没有了子类共享父类引用属性的问题。
但这种模式也有他的问题,那就是在父类原型中定义的方法,实际上是对子类不可见的。

2.3组合继承

既然上述的两种方式各有各自的局限性,将它俩整合到一块儿是否是会好一点呢,因而就有了组合继承。

function Super(){
    this.val = true;
    this.arr = ["a"];
}
function Sub(){
       Super.call(this);                    //{2}
}
Sub.prototype = new Super();                //{1}
Sub.prototype.constructor = Sub;            //{3}
var sub = new Sub();
console.log(sub.val)        //true

组合继承还有一个要注意的地方:
在代码{3}处,将子类原型的constructor属性指向子类的构造函数。由于若是不这么作,子类的原型是父类的一个实例,因此子类原型的constructor属性就丢失了,他会顺着原型链继续往上找,因而就找到了父类的constructor因此它指向的实际上是父类。

这种继承方式是使用最多的一种方式。
这种继承方式解决了上两种方式的缺点,不会出现共享引用类型的问题,同时父类原型中的方法也被继承了下来。

若是要提及有什么缺点咱们发现,在执行代码{1}时,Sub.prototype会获得父类型的val和arr两个属性。他们是Super的实例属性,只不过如今在Sub的原型上。而代码{2}处,在建立Sub实例的时候,调用Super的构造函数,又会在新的对象上建立属性val和arr,因而,这两个属性就屏蔽了原型中两个同名属性。

2.4寄生组合式继承

对于上面的问题,咱们也有解决办法,不是在子类原型中多了一份父类的属性和方法么,那我原型中就只要父类原型中的属性和方法,这里咱们引入了一个方法:

function inheritObject(obj){
    var F = function(){};
    F.prototype = obj;
    return new F();
}

这个方法建立了一个对象临时性的构造函数,而后将传入的对象做为这个构造函数的原型,最后返回这个临时类型的一个新实例。

咱们能够设想,若是用这个方法拷贝一份父类的原型属性给子类,是否是就避免了上面提到的子类原型中多了一份父类构造函数内的属性。看以下代码:

function Super(){
    this.val = 1;
    this.arr = [1];
}
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};

function Sub(){
    Super.call(this);
}
var p = inheritObject(Super.prototype);         //{1}
p.constructor = Sub;                            //{2}
Sub.prototype = p;                              //{3}
 
var sub = new Sub();

基本思路就是:没必要为了指定子类型的原型而调用父类的够着函数,咱们须要的无非就是父类原型的一个副本而已。本质上就是复制出父类的一个副本,而后再将结果指定给子类型的原型。

3、多态

所谓多态,就是同一个方法的多种调用方式,在javascript中,经过arguments对象对传入的参数作判断就能够实现多种调用方式。

例子:

function Add(){
    function zero(){
        return 10;
    }
    function one(num){
        return 10 + num;
    }
function    two(num1, num2){
    return num1 + num2;
}
this.add = function(){
    var arg = arguments,
            len = arg.length;
    switch (len){
        case 0:
            return zero();
        case 1:
            return one(arg[0]);
        case 2:
            return two(arg[0], arg[1]);
        }
    }
}
var A = new Add();
console.log(A.add());                //10
console.log(A.add(5));              //15
console.log(A.add(6, 7));          //13
相关文章
相关标签/搜索