上一篇咱们讲到了,在前端面试的时候常被问到的函数及函数做用域的问题。今天这篇咱们讲js的一个比较重要的甚至在编程的世界都很重要的问题 面向对象 。javascript
“一切皆对象!” 你们都对此深信不疑。其实否则,这里面带有不少的语言陷阱,仍是不要处处给别人吹嘘一切皆对象为好。css
JavaScript 是一种弱类型或者说动态语言。这意味着你不用提早声明变量的类型,在程序运行过程当中,类型会被自动肯定。这也意味着你可使用同一个变量保存不一样类型的数据,最新的 ECMAScript 标准定义了 7 种数据类型:html
基本类型前端
对象类型java
对象类型涵盖了不少引用类型,任何非基本类型的都是对象类型。如Function(函数Function 是一个附带可被调用功能的常规对象),这里就不在赘述。git
根据这个分类能够看出“并不是一切接对象”。github
咱们能够从两方面来区别这两种类型:面试
可变性编程
基本类型:不可变类型,没法添加属性;即便添加属性,解析器没法再下一步读取它;数组
var cat = "cat";
cat.color = "black";
cat.color // undefined
复制代码
对象类型:可变类型,支持添加和删除属性。
比较和传递
基本类型:按值比较,按值传递;
对象类型:按引用比较,按引用传递。
// 基本类型
var cat = "tom";
var dog = "tom";
cat === dog // true
//对象类型
var cat = {name:"tom"};
var dog = {name:"tom"};
cat === dog //false
复制代码
咱们说的经过引用进行对象比较是:两个对象的值是否相同取决于它们是否指向相同的底层对象 __David Flanagan
因此咱们改为这样:
var cat = {name:"tom"}
var dog = cat;
b.name = "Haba"
dog === cat // true
复制代码
检测一个对象的类型,强烈推荐使用 Object.prototype.toString 方法; 由于这是惟一一个可依赖的方式。 咱们使用Object.prototype.toString方法:
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(2) // "[object Number]"
复制代码
为何不能用typeOf ?
typeof只有在基本类型的检测上面才好使,在引用类型(Function除外)里面他返回的都是object,另 typeof null === "object".
“面向对象编程(OOP)” 是目前主流的编程范式,其核心思想是将真实世界中各类复杂的关系,抽象为一个个对象,而后由对象之间的分工与合做,完成对真实世界的模拟。
对象是单个实物的抽象,一本书,一只猫,一我的均可以是对象。从语言的角度对象就是一个容器封装了属性和方法。
典型面向对象的两大概念:类和 实例
很遗憾JavaScript没有类的概念,但它使用构造函数(constructor)做为对象的模板。
//构造函数
var Pet = function (name, language) {
this.name = name;
this.say = function () {
console.log(language);
}
}
// new 关键字生成对象 有关new操做符咱们在后面会讲到。
var cat = new Pet('tom', 'meow');
cat.name // tom
cat.say() // meow
复制代码
new用于新建一个对象,例如:
function Pet () {}
var tom = new Pet();
复制代码
new进行了以下操做:
function newObj(Fun,arguments) {
var o = {};
if (Fun && typeof Fun === "function") {
o.__proto__ = Fun.prototype;
Fun.apply(o, arguments);
return o;
}
}
复制代码
这里须要注意的是,构造函数内部有return语句的状况。若是return 后面跟着一个对象,new命令返回return指定的对象;不然无论return语句直接返回this.
var Pet = function (name) {
this.name = name;
return {notInstance:"blabla"}
}
var cat = new Pet('tom');
cat.name // undefined
cat.notInstance // blabla
复制代码
上面的讲到的构造函数,实例对象的属性和方法都在构造函数内部实现。这样的 构造函数有一个缺点:
var cat1 = new Pet('tom', 'meow');
var cat2 = new pet('jery', 'meow');
cat1.say === cat2.say // false
复制代码
生成两只猫 叫声同样,可是猫的say方法是不同的,就是说每新建一个对象就生成一个新的say方法。全部的say方法都是一样的行为,彻底能够共享。
JavaScript的原型(prototype)可让咱们实现共享。
JavaScrip能够采用构造器(constructor)生成一个新的对象,每一个构造器都拥有一个prototype属性,而每一个经过此构造器生成的对象都有一个指向该构造器原型(prototype)的内部私有的连接(proto),而这个prototype由于是个对象,它也拥有本身的原型,这么一级一级直到原型为null,这就构成了原型链.
原型链的工做原理:
function getProperty(obj, prop) {
if (obj.hasOwnProperty(prop)) //首先查找自身属性,若是有则直接返回
return obj[prop]
else if (obj.__proto__ !== null)
return getProperty(obj.__proto__, prop) //如何不是私有属性,就在原型链上一步步向上查找,直到找到,若是找不到就返回undefind
else
return undefined
}
复制代码
若是跟着原型链一层层的寻找,全部对象均可以寻找到最顶层,Object.prototype, 即Object的构造函数的prototype属性,而Object.prototype对象指向的就是没有任何属性和方法的null对象。
Object.getPrototypeOf(Object.prototype)
// null
复制代码
原型链代表了一个对象查找他的属性的过程:首先在对象自己上面找 -> 没找到再到对象的原型上找 ->仍是找不到就到原型的原型上找 —>直到Object.prototype找不到 -> 返回undefined。(在这种查找中找到就马上返回)。
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
因为constructor属性是一种原型对象与构造函数的关联关系,因此修改原型对象的时候,务必要当心。
var Pet = function (name) {
this.name = name;
}
// 尽可能避免这么写,由于会把construct属性覆盖掉。
Pet.prototype = {
say: function () {
console.log('meow');
}
}
// 若是咱们覆盖了constructor属性要记得将他指回来。
Pet.prototype.constructor = Pet;
复制代码
prototype是function对象中专有的属性。
__proto__是普通对象的隐式属性,在new的时候,会指向prototype所指的对象;
__proto__其实是某个实体对象的属性,而prototype则是属于构造函数的属性。
__proto__只能在学习或调试的环境下使用。
这里抓住两点:
Object 为构造函数时,是Function的实例对象;Function为构造函数时,Function.prototype 是对象,那么他就是Object的实例对象。
来看一个题目:
var F = function(){};
Object.prototype.a = function(){};
Function.prototype.b = function(){};
var f = new F();
// f 能取到a,b吗?原理是什么?
复制代码
根据原型链的关系:
f是F的实例对象,其原型链:
f.__proto__ -> [F prototype].__proto__ -> [Object prototype].__proto__ -> null
复制代码
F是构造函数,是Function的实例,他的原型链:
F.__proto__ -> [Function prototype].__proto__ -> [Object prototype].__proto__ -> null
复制代码
由此,只有F可以访问到Function的prototype,答案就是:“f只能a,可是F能够访问a,b”
原型继承是借助已有的对象建立新的对象,将子类的原型指向父类,就至关于加入了父类这条原型链。
function Animal(){
this.super = 'animal';
}
function Cat(name) {
this.name = name;
this.food = 'fish';
}
Cat.prototype = new Animal(); // Cat 继承了Animal
Cat.prototype.getFood = function () {
return this.food;
}
复制代码
上面的方法中constructor指向有点问题:
var cat = new Cat('tom');
cat.name // tom
cat.super // animal
cat.getFood() // fish
//but
cat.constructor === Cat //false
cat.constructor === Animal //true
复制代码
cat 的constructor 并无指向Cat而是指向了父类Animal。咱们须要对它进行修正:
function Cat(name){
this.name = name;
this.food = 'fish';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
Cat.prototype.getFood = function () {
return this.food;
}
复制代码
好了上面就实现了一个简单的原型继承。
关于面向对象,我想说js “几乎一切皆对象”,由于有原型链的存在咱们能实现相似其余语言的继承。
加上前面一篇,两篇文章已经涵盖了js大半部分面试问题了,接下来的文章可能会讲解一下单线程模型和计时器相关。这块是个难点我也是看了好多资料后才搞明白了大概。此次的面试系列主要仍是针对于“中高级前端”,也是一个进阶的层次,各位看官不要灰心一切都会有拨云见日的一天。
女排夺冠了,今年好像好多我关注的时间都有了完美的结局,非常为他们高兴,也但愿个人将来也是一个完美的结局,晚安。
请关注个人专栏 《前端杂货铺》