之前遇到过一次关于JS面向对象的面试题。虽说也答出来了,但事后总感觉缺少一些什么,后来在网上各种收集一些信息,汇集如下。
如果你面试的时候,面试官问你怎么理解js面向对象?并不需要你滔滔不绝,抓住重点描述即可。毕竟没人喜欢听另一个人在那滔滔不绝,说个不停。甚至有可能对方并不懂这么多,也只是随口一问而已。
前端关于js面对对象的面试题(部分):
Q:怎么理解js面向对象?
A:世间万物皆对象,对象有具体的的实例化,任何方法或者属性都要写在对象(类)里面。面向对象只是过程式代码的一种高度封装,目的在于提高代码的开发效率和可维护性。
在js中对象是一种语法,可以定义属性和方法, 面向对象开发就是将项目中的元素抽象成一个一个的对象,并定义相关内容的属性和方法来进行相互的调用,在其他的编程语言中直接就是面向对象开发,有封装,继承,多态三大特性,在js中可以使用原型模拟出继承效果。
面向对象的优点?
(1)提高了代码的可扩展性。
(2)提高了代码的可维护性。
(3)面向对象的封装,继承,多态。
Q:什么是面向对象编程?
A:面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。
它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。
因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。
Q:面向对象和面向过程?
A:
- 面向过程就是亲力亲为,事无巨细,面面俱到,步步紧跟,有条不紊
- 面向对象就是找一个对象,指挥的结果
- 面向对象将执行者转变成指挥者
- 面向对象不是面向过程的替代,而是面向过程的封装
以下内容详细描述js面向对象,适合群体:使用过 JS 框架但对 JS 语言本质缺乏理解的程序员,具有 Java、C++ 等语言开发经验,准备学习并使用 JavaScript 的程序员,以及一直对 JavaScript 是否面向对象模棱两可,但希望知道真相的 JS 爱好者。
认识面向对象
为了说明 JavaScript 是一门彻底的面向对象的语言,首先有必要从面向对象的概念着手 , 探讨一下面向对象中的几个概念:
- 一切事物皆对象
- 对象具有封装和继承特性
- 对象与对象之间使用消息通信,各自存在信息隐藏
以这三点做为依据,C++ 是半面向对象半面向过程语言,因为,虽然他实现了类的封装、继承和多态,但存在非对象性质的全局函数和变量。Java、C# 是完全的面向对象语言,它们通过类的形式组织函数和变量,使之不能脱离对象存在。但这里函数本身是一个过程,只是依附在某个类上。
然而,面向对象仅仅是一个概念或者编程思想而已,它不应该依赖于某个语言存在。比如 Java 采用面向对象思想构造其语言,它实现了类、继承、派生、多态、接口等机制。但是这些机制,只是实现面向对象编程的一种手段,而非必须。换言之,一门语言可以根据其自身特性选择合适的方式来实现面向对象。所以,由于大多数程序员首先学习或者使用的是类似 Java、C++ 等高级编译型语言(Java 虽然是半编译半解释,但一般做为编译型来讲解),因而先入为主地接受了“类”这个面向对象实现方式,从而在学习脚本语言的时候,习惯性地用类式面向对象语言中的概念来判断该语言是否是面向对象语言,或者是否具备面向对象特性。这也是阻碍程序员深入学习并掌握 JavaScript 的重要原因之一。
实际上,JavaScript 语言是通过一种叫做 原型( prototype
)的方式来实现面向对象编程的。下面就来讨论 基于类的(class-based)面向对象和 基于原型的 (prototype-based) 面向对象这两种方式在构造客观世界的方式上的差别。
基于类的面向对象和基于原型的面向对象方式比较
在基于类的面向对象方式中,对象( object
)依靠 类( class
)来产生。而在基于原型的面向对象方式中,对象( object
)则是依靠 构造器( constructor
)利用 原型( prototype
)构造出来的。举个客观世界的例子来说明二种方式认知的差异。例如工厂造一辆车,一方面,工人必须参照一张工程图纸,设计规定这辆车应该如何制造。这里的工程图纸就好比是语言中的 类 ( class
),而车就是按照这个 类( class
)制造出来的;另一方面,工人和机器 ( 相当于 constructor
) 利用各种零部件如发动机,轮胎,方向盘 ( 相当于 prototype
的各个属性 ) 将汽车构造出来。
事实上关于这两种方式谁更为彻底地表达了面向对象的思想,目前尚有争论。但笔者认为原型式面向对象是一种更为彻底的面向对象方式,理由如下:
首先,客观世界中的对象的产生都是其它实物对象构造的结果,而抽象的“图纸”是不能产生“汽车”的,也就是说,类是一个抽象概念而并非实体,而对象的产生是一个实体的产生;
其次,按照一切事物皆对象这个最基本的面向对象的法则来看,类 (class
) 本身并不是一个对象,然而原型方式中的构造器 (constructor
) 和原型 (prototype
) 本身也是其他对象通过原型方式构造出来的对象。
再次,在类式面向对象语言中,对象的状态 (state
) 由对象实例 (instance
) 所持有,对象的行为方法 (method
) 则由声明该对象的类所持有,并且只有对象的结构和方法能够被继承;而在原型式面向对象语言中,对象的行为、状态都属于对象本身,并且能够一起被继承,这也更贴近客观实际。
最后,类式面向对象语言比如 Java,为了弥补无法使用面向过程语言中全局函数和变量的不便,允许在类中声明静态 (static
) 属性和静态方法。而实际上,客观世界不存在所谓静态概念,因为一切事物皆对象!而在原型式面向对象语言中,除内建对象 (build-in object
) 外,不允许全局对象、方法或者属性的存在,也没有静态概念。所有语言元素 (primitive
) 必须依赖对象存在。但由于函数式语言的特点,语言元素所依赖的对象是随着运行时 (runtime
) 上下文 (context
) 变化而变化的,具体体现在 this
指针的变化。正是这种特点更贴近 “万物皆有所属,宇宙乃万物生存之根本”的自然观点。在 程序 示例1中 window 便类似与宇宙的概念。
示例 1. 对象的上下文依赖
var str = "我是一个 String 对象 , 我声明在这里 , 但我不是独立存在的!"
var obj = {
des: "我是一个 Object 对象 , 我声明在这里,我也不是独立存在的。"
};
var fun = function() {
console.log("我是一个Function 对象!谁调用我, 我属于谁:", this);
};
obj.fun = fun;
console.log(this === window); // 打印 true
console.log(window.str === str); // 打印 true
console.log(window.obj === obj); // 打印 true
console.log(window.fun === fun); // 打印 true
fun(); // 打印 我是一个 Function对象!谁调用我, 我属于谁:属于window
obj.fun(); // 打印 我是一个 Function 对象!谁调用我,我属于谁:属于obj
fun.apply(str); // 打印 我是一个 Function 对象!谁调用我, 我属于谁:属于str
在接受了面向对象存在一种叫做基于原型实现的方式的事实之后,下面我们就可以来深入探讨 ECMAScript 是如何依据这一方式构造自己的语言的。
JavaScript 类
详情参考:JS中Class类是什么
1.什么是类(Class)?
具有相同特性(数据元素)和行为(功能)的对象的抽象就是类。因此,对象的抽象是类,类的具体化就是对象,也可以说类的实例是对象,类实际上就是一种数据类型。类具有属性,它是对象状态的抽象,用数据结构来描述类的属性。类具有操作,它是对象行为的抽象,用操作名和实现该操作的方法来描述。
类映射的每一个对象都具有这些数据和操作方法,类的继承具有层次性和结构性,高层次对象封装复杂行为,具体细节对该层次知识保持透明,可以减小问题求解的复杂度。
2.类和对象的区别
作为初学者,容易混淆类和对象的概念。类(Class)是一个抽象的概念,对象则是类的具体实例。比如:人是一个类,司马迁,李白,杜甫都是对象;首都是一个类,则北京,伦敦,华盛顿,莫斯科都是对象;动物猫是一个类,则Kitty、Grafield 和 Doraemon 都是对象。
我们可以说 Kitty 猫的体重是 1.5kg,而不能说猫类的体重是 1.5kg;可以说李白是诗人,而不能说人类是诗人。状态是描述具体对象而非描述类的,行为是由具体对象发出的而非类发出的。
3.类和对象的关系
类与对象的关系就如模具和铸件的关系,类实例化的结果就是对象,而对象的抽象就是类,类描述了一组有相同特性(属性)和相同行为的对象。
class person{ }//这个是类
$obj = new person();//类的实例化就是对象
JavaScript 原型
1.什么是原型?
Javascript规定,每一个函数都有一个 prototype
对象属性,指向另一个对象(原型链上面的)。
prototype
(对象属性)的所有属性和方法,都会被构造函数的实例继承。这意味着,我们可以把那些不变(公用)的属性和方法,直接定义在 prototype
对象属性上。
prototype
就是调用构造函数所创建的那个实例对象的原型(proto
)。
prototype
可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
2.什么是原型链?
每个对象都有一个原型 __proto__
,这个原型还可以有它自己的原型,以此类推,形成一个原型链。当我们访问实例对象的某个属性的时候,我们先去这个对象本身的属性上去找,如果没有的话,就通过 __proto__
属性 去它的原型对象里面去,如果还是没有的话,则会在构造函数的原型的__proto__
中去找,这样一层层向上查找就会形成一个作用域链,称为原型链。
console.log(Object.prototype.__proto__ === null) // true
function Person() {}
Person.prototype.name = "HUGH"
var person = new Person();
console.log(person.name) // HUGH
console.log(person) // Person {}
console.log(person.__proto__) // {name: 'HUGH', constructor: ƒ}
console.log(person.__proto__.name) // HUGH
console.log(person.__proto__.constructor) // ƒ Person() {}
console.log(person.__proto__.constructor.prototype) // {name: 'HUGH', constructor: ƒ}
console.log(person.__proto__ === person.__proto__.constructor.prototype) // true
在原型链中的指向是:函数 → 构造函数 → Function.prototype → Object.protype → null ;
JavaScript 私有成员实现
1.基于编码规范约定实现方式
很多编码规范把以下划线_
开头的变量约定为私有成员,便于同团队开发人员的协同工作。实现方式如下:
function Person(name) {
this._name = name;
}
var person = new Person('Joe');
这种方式只是一种规范约定,很容易被打破。而且也并没有实现私有属性,上述代码中的实例person可以直接访问到_name
属性:
alert(person._name); //'Joe'
2.基于闭包的实现方式
另外一种比较普遍的方式是利用JavaScript的闭包特性。构造函数内定义局部变量和特权函数,其实例只能通过特权函数访问此变量,如下:
function Person(name) {
var _name = name;
this.getName = function() {
return _name;
}
}
var person = new Person('Joe');
这种方式的优点是实现了私有属性的隐藏,Person 的实例并不能直接访问_name
属性,只能通过特权函数getName获取:
alert(person._name); // undefined
alert(person.getName()); //'Joe'
使用闭包和特权函数实现私有属性的定义和访问是很多开发者采用的方式,Douglas Crockford也曾在博客中提到过这种方式。但是这种方式存在一些缺陷:
- 私有变量和特权函数只能在构造函数中创建。通常来讲,构造函数的功能只负责创建新对象,方法应该共享于
prototype
上。特权函数本质上是存在于每个实例中的,而不是prototype
上,增加了资源占用。
JavaScript 被认为是世界上最受误解的编程语言,但随着近些年来 Web 应用的普及和 JS 语言自身的长足发展,特别是后台 JS 引擎的出现 ( 如基于 V8 的 NodeJS 等 ),可以预见,原来只是作为玩具编写页面效果的 JS 将获得更广阔发展天地。这样的发展趋势,也对 JS 程序员提出了更高要求。只有彻底领悟了这门语言,才有可能在大型的 JS 项目中发挥它的威力。