前言

前一阵面试,过程中发现问到一些很基础的问题时候,自己并不能很流畅的回答出来。或者遇到一些基础知识的应用,由于对这些点理解的不是很深入,拿着笔居然什么都写不出来,于是有了回顾一下这些基础知识的想法。

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

这一部分的主要参考资料:《JavaScript高级程序设计》、《JavaScript设计模式》

如果有什么错误的地方,也希望看到这篇文章的小伙伴给我指出来,谢谢 _

一、对象

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变量添加属性或者方法来实现对类添加属相或者方法。

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

二、继承

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();

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

三、多态

所谓多态,就是同一个方法的多种调用方式,在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

前端面试回顾(1)---javascript的面向对象的更多相关文章

  1. web前端面试试题总结---javascript篇

    JavaScript 介绍js的基本数据类型. Undefined.Null.Boolean.Number.String. ECMAScript 2015 新增:Symbol(创建后独一无二且不可变的 ...

  2. 前端面试回顾---javascript的面向对象

    转:https://segmentfault.com/a/1190000011061136 前言 前一阵面试,过程中发现问到一些很基础的问题时候,自己并不能很流畅的回答出来.或者遇到一些基础知识的应用 ...

  3. 前端面试准备之JavaScript

    1.数据类型. JavaScript中有5种简单数据类型(也称为基本数据类型):Undefined.Null.Boolean.Number和String.还有1种复杂数据类型——Object,Obje ...

  4. web前端面试试题总结---html篇

    HTML Doctype作用?标准模式与兼容模式各有什么区别? (1).<!DOCTYPE>声明位于位于HTML文档中的第一行,处于 <html> 标签之前.告知浏览器的解析器 ...

  5. web前端面试总结

    本文由我收集总结了一些前端面试题,初学者阅后也要用心钻研其中的原理,重要知识需要系统学习.透彻学习,形成自己的知识链.万不可投机取巧,临时抱佛脚只求面试侥幸混过关是错误的!也是不可能的! 前端还是一个 ...

  6. web前端面试试题总结---其他

    其他问题 原来公司工作流程是怎么样的,如何与其他人协作的?如何夸部门合作的? 你遇到过比较难的技术问题是?你是如何解决的? 设计模式 知道什么是singleton, factory, strategy ...

  7. web前端面试试题总结---css篇

    CSS 介绍一下标准的CSS的盒子模型?低版本IE的盒子模型有什么不同的? (1)有两种, IE 盒子模型.W3C 盒子模型: (2)盒模型: 内容(content).填充(padding).边界(m ...

  8. 问得最多的十个JavaScript前端面试问题

    我知道有很多人不同意这种类型的面试.其实不管你喜不喜欢,你都得接受.尤其当你是自学的,而且要申请第一份工作时.   我估计很多有人其它方法来证明他自己,像Github/ 项目地址可能是非常理想的证明方 ...

  9. 前端面试之JavaScript的基本数据类型!

    前端面试之JavaScript的基本数据类型! JS的基本数据类型 数字 字符串 布尔值 JavaScript中有两个特殊的原始值: null (空) 和undefined (未定义), , 它们不是 ...

随机推荐

  1. 2021-02:Teams开发平台更新概述

    作为2021年工作计划的一部分,我会在每月的第三个星期五发布Teams开发平台的更新报告,给大家整理和讲解最新的平台功能,以及特色场景.这是第一篇文章,我会挑选截至到2月份一些重要的更新,以后每月的更 ...

  2. Java操作Excel工具类(poi)

    分享一个自己做的poi工具类,写不是很完全,足够我自己当前使用,有兴趣的可以自行扩展 1 import org.apache.commons.lang3.exception.ExceptionUtil ...

  3. 使用Mongodb设计评论系统

    1:如何设计数据存储结构 1.1:mysql 1:评论表 2:回复表(评论的评论) 1.2:mongodb 不需要两张表,一个collection 就可以搞定. 数据结构如图: 通过对象数组中的字段作 ...

  4. Redis 内存淘汰机制详解

    一般来说,缓存的容量是小于数据总量的,所以,当缓存数据越来越多,Redis 不可避免的会被写满,这时候就涉及到 Redis 的内存淘汰机制了.我们需要选定某种策略将"不重要"的数据 ...

  5. .NET并发编程-数据并行

    本系列学习在.NET中的并发并行编程模式,实战技巧 内容目录 数据并行Fork/Join模式PLINQ 本小节开始学习数据并行的概念模式,以及在.NET中数据并行的实现方式.本系列保证最少代码呈现量, ...

  6. 《C++ Primer》笔记 第6章 函数

    任意两个形参都不能同名,而且函数最外层作用域中的局部变量也不能使用与函数形参一样的名字(形参就相当于该函数的局部变量). 形参名是可选的,但是由于我们无法使用未命名的形参,所以形参一般都应该有个名字. ...

  7. Hi3559AV100板载开发系列-pthread_create()下V4L2接口MJPEG像素格式的VIDIOC_DQBUF error问题解决-采用阻塞方式下select监听

     最近一直加班加点进行基于Hi3559AV100平台的BOXER-8410AI板载开发,在开发的过程中,遇到了相当多的问题,其一是板载的开发资料没有且功能不完整,厂家不提供太多售后技术支持,厂家对部分 ...

  8. 抽一根烟的时间学会.NET Core 操作RabbitMQ

    什么是RabbitMQ? RabbitMQ是由erlang语言开发的一个基于AMQP(Advanced Message Queuing Protocol)协议的企业级消息队列中间件.可实现队列,订阅/ ...

  9. 用vue.js实现的期货,股票的实时K线

    用vue.js实现的期货,股票的实时k线 项目地址:https://github.com/zhengquantao/vue-Kline vue-kline 效果图 Build Setup 本项目基于V ...

  10. External Libraries中没有Maven的jar包的原因(已解决)

    **深坑!** ## External Libraries中没有Maven的jar包的原因(已解决) 2021年3月1日 --- 搭建一个新项目 IDEA 从 Git 上拉 拉去Maven项目然后 m ...