上一篇 我们讲到了,在前端面试的时候常被问到的函数及函数作用域的问题.今天这篇我们讲 js 的一个比较重要的甚至在编程的世界都很重要的问题 面向对象 .
在 JavaScript 中一切都是对象吗?
"一切皆对象!" 大家都对此深信不疑.其实不然,这里面带有很多的语言陷阱,还是不要到处给别人吹嘘一切皆对象为好.
数据类型
JavaScript 是一种弱类型或者说动态语言.这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定.这也意味着你可以使用同一个变量保存不同类型的数据,最新的 ECMAScript 标准定义了 7 种数据类型:
基本类型
Symbol (ECMAScript 6 新定义)
Boolean
Null
Undefined
Number
String
对象类型
Object
对象类型涵盖了很多引用类型,任何非基本类型的都是对象类型.如 Function(函数 Function 是一个附带可被调用功能的常规对象),这里就不在赘述.
根据这个分类可以看出 "并非一切接对象".
我们可以从两方面来区别这两种类型:
区别
可变性
基本类型:不可变类型,无法添加属性;即使添加属性,解析器无法再下一步读取它;
对象类型:可变类型,支持添加和删除属性.
var cat = "cat";
cat.color = "black";
cat.color // undefined
比较和传递
基本类型:按值比较,按值传递;
对象类型:按引用比较,按引用传递.
我们说的通过引用进行对象比较是: 两个对象的值是否相同取决于它们是否指向相同的底层对象 __David Flanagan
// 基本类型
var cat = "tom";
var dog = "tom";
cat === dog // true
//对象类型
var cat = {name:"tom";
var dog = {name:"tom";
cat === dog //false
所以我们改成这样:
如何检测对象类型?或者怎么检测一个数据是数组类型?
var cat = {name:"tom"
var dog = cat;
b.name = "Haba"
dog === cat // true
检测一个对象的类型,强烈推荐使用 Object.prototype.toString 方法; 因为这是唯一一个可依赖的方式. 我们使用 Object.prototype.toString 方法:
为什么不能用 typeOf?
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(2) // "[object Number]"
typeof 只有在基本类型的检测上面才好使,在引用类型 (Function 除外) 里面他返回的都是 object,另 typeof null === "object".
简谈面向对象
"面向对象编程(OOP)" 是目前主流的编程范式,其核心思想是将真实世界中各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟.
对象是单个实物的抽象,一本书,一只猫,一个人都可以是对象.从语言的角度对象就是一个容器封装了属性和方法.
典型面向对象的两大概念:类和 实例
类:对象的类型模板
实例:根据类创建的对象
很遗憾 JavaScript 没有类的概念,但它使用构造函数(constructor)作为对象的模板.
new 创建一个对象都进行了哪些操作?
//构造函数
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 用于新建一个对象,例如:
new 进行了如下操作:
function Pet() {}
var tom = new Pet();
创建一个空对象,用 this 变量引用该对象并继承该函数的原型
属性和方法加入到 this 的引用对象中
新创建的对象由 this 所引用,并且最后隐式的返回 this
模拟过程:
这里需要注意的是,构造函数内部有 return 语句的情况.如果 return 后面跟着一个对象,new 命令返回 return 指定的对象;否则不管 return 语句直接返回 this.
function newObj(Fun, arguments) {
var o = {};
if (Fun && typeof Fun === "function") {
o.__proto__ = Fun.prototype;
Fun.apply(o, arguments);
return o;
}
}
阐述原型链?js 如何实现继承?
var Pet = function (name) {
this.name = name;
return {notInstance:"blabla"
}
var cat = new Pet('tom');
cat.name // undefined
cat.notInstance // blabla
上面的讲到的构造函数,实例对象的属性和方法都在构造函数内部实现.这样的 构造函数有一个缺点:
生成两只猫 叫声一样,但是猫的 say 方法是不一样的,就是说每新建一个对象就生成一个新的 say 方法.所有的 say 方法都是同样的行为,完全可以共享.
var cat1 = new Pet('tom', 'meow');
var cat2 = new pet('jery', 'meow');
cat1.say === cat2.say // false
JavaScript 的原型(prototype)可以让我们实现共享.
原型链 ?
JavaScrip 可以采用构造器 (constructor) 生成一个新的对象, 每个构造器都拥有一个 prototype 属性, 而每个通过此构造器生成的对象都有一个指向该构造器原型 (prototype) 的内部私有的链接(proto), 而这个 prototype 因为是个对象, 它也拥有自己的原型, 这么一级一级直到原型为 null, 这就构成了原型链.
原型链的工作原理:
如果跟着原型链一层层的寻找,所有对象都可以寻找到最顶层,Object.prototype, 即 Object 的构造函数的 prototype 属性,而 Object.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 找不到 -> 返回 undefined.(在这种查找中找到就立刻返回).
Object.getPrototypeOf(Object.prototype)
// null
constructor 属性
prototype 对象有一个 constructor 属性,默认指向 prototype 对象所在的构造函数.
由于 constructor 属性是一种原型对象与构造函数的关联关系,所以修改原型对象的时候,务必要小心.
__proto__ 属性和 prototype 属性的区别
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__只能在学习或调试的环境下使用.
这里抓住两点:
构造函数通过 prototype 属性访问原型对象
实例对象通过 [[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"
原型继承
原型继承是借助已有的对象创建新的对象,将子类的原型指向父类,就相当于加入了父类这条原型链.
上面的方法中 constructor 指向有点问题:
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;
}
cat 的 constructor 并没有指向 Cat 而是指向了父类 Animal.我们需要对它进行修正:
var cat = new Cat('tom');
cat.name // tom
cat.super // animal
cat.getFood() // fish
//but
cat.constructor === Cat //false
cat.constructor === Animal //true
好了上面就实现了一个简单的原型继承.
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 特色的一直实现方式,也是使用最多的方式.
关于面向对象,我想说 js "几乎一切皆对象",因为有原型链的存在我们能实现类似其他语言的继承.
加上 前面一篇 , 两篇文章已经涵盖了 js 大半部分面试问题了,接下来的文章可能会讲解一下单线程模型和计时器相关.这块是个难点我也是看了好多资料后才搞明白了大概.这次的 面试系列 主要还是针对于 "中高级前端",也是一个进阶的层次,各位看官不要灰心一切都会有拨云见日的一天.
女排夺冠了,今年好像好多我关注的时间都有了完美的结局,很是为他们高兴,也希望我的未来也是一个完美的结局,晚安.
请关注我的专栏 《前端杂货铺》
参考文章
prototype 对象
来源: https://juejin.im/post/5a570c61f265da3e243b6e16