《前端之路》之 JavaScript原型及原型链详解
05:JS 原型链
在 JavaScript 的世界中,万物皆对象! 但是这各种各样的对象其实具体来划分的话就 2 种。 一种是 函数对象
,剩下的就是 普通对象
。其中 Function
和 Object
为JS自带的 函数对象
。(哎? 等等, Function 为 函数对象 可以理解,为什么 Object
也是函数对象呢?带着疑问我们继续往下看。 )
Function 和 Object 为何都是 函数对象呢?
一. 普通对象 和 函数对象
- 函数对象 的疑惑 🤔
var f1 = new Function('arg', 'console.log(arg)')
var test1 = new Object
var f3 = new Object()
console.log(typeof f1) // function
console.log(typeof test1) // object
console.log(typeof f3) // object
经过 new 实例化过后的 test1 和 f3 均为 Object 即为 普通对象。
那如果我们直接 打印出来 未实例化的对象 的类型 呢?
var test2 = Object
var test3 = Object()
console.log(typeof test2) // function
console.log(typeof test3) // object
这里我们看到了一些区别,但是这又是为什么呢?
下面我们看完普通对象再纵向的进行一下对比
- 什么是普通对象? 🤔
我们先想想一下,创建对象的方式?
var F1 = function() {}
var o1 = {}
var o2 = new F1()
var o3 = new Object()
console.log(typeof o1) // object
console.log(typeof o2) // object
console.log(typeof o3) // object
- 直接纵向对比这 两类 对象
function f1() {}
var f2 = function() {}
var f3 = new Function('arg', 'console.log(arg)')
var o1 = {}
var o2 = new Object()
var o3 = new f1()
console.log(typeof f1) // function
console.log(typeof f2) // function
console.log(typeof f3) // function
console.log(typeof o1) // object
console.log(typeof o2) // object
console.log(typeof o3) // object
// 对比前文中的 Function 和 Object
console.log(typeof Function) // function
console.log(typeof Object) // function
在上面的例子中 o1 o2 o3 为普通对象
,f1 f2 f3 为函数对象
。怎么区分,其实很简单,凡是通过 new Function()
创建的对象都是函数对象
,其他的都是普通对象
。f1,f2,归根到底
都是通过 new Function()的方式进行创建的。Function Object 也都是通过 New Function()创建的。
-
f1,f2,归根到底都是通过 new Function()的方式进行创建的。
这么来理解这句话呢?
function fns(a, b) {
return a + b
}
// 等价于
var fns = new Function('a, b', 'return a + b')
-
上面的代码做了一个同目标的不同实现实现方法,那么为什么两种方案的结果都是相同的呢?
讲到这里就需要对 JavaScript 这门语言进行 分析了。
JavaScript 是一门解释型的语言
什么是 解释型 语言?
在客户端的浏览器中存在一个 可以解释 JS 的`引擎`。 这里JavaScript的引擎 就是 谷歌的 V8 引擎 和 其他浏览器引擎。
而我们常常听说的 ES5 ES6 什么的,往往指的是当前 JS 语言的版本。或者说当前的 JS 语言的标准。 然后所谓的浏览器兼容这些新的特性其实就是浏览器的 JS 引擎的升级,去适配这些新版本的 JS 的新特性。 不兼容,往往就是 引擎 不支持这个新特性。
(所以,我们会发现写一个 浏览器 应用 还是很难的。 因为你需要去兼容这么的东西,最新版本的JS,css3 最新的特性 , html5 的新标签,等等)
那么我们回到 解释型语言 上来,有了能解释 JS 语句的引擎了,那么上面就一定会有一定的规则了,不然的话,如果你乱写都能被 引擎 读懂的话,要这 引擎 何用。
好,上段 中介绍到了 规则的问题, 那么 JS 语言本身肯定也是隐藏了一些 我们在 ES 系列上看不到的 规则。 那是什么呢?
我们一起来看下,
这就是本小节将要介绍的函数对象(Function Object)。
函数对象与其它用户所定义的对象有着本质的区别,这一类对象被称之为内部对象,例如日期对象(Date)、数组对象(Array)、字符串对象(String)都是属于内部对象。换句话说,这些内置对象的构造器是由JavaScript本身所定义的:通过执行new Array()这样的语句返回一个对象,JavaScript 内部有一套机制来初始化返回的对象,而不是由用户来指定对象的构造方式。
这些内置对象的构造器是由JavaScript本身所定义的
所以: new Fucntion('arg', console.log(arg))
new Array()
new Date()
new String()
都会返回对应的 对象。 所以,当我们在用 字面量 去创建一个 函数的时候,JS 解释器就会 用这些 内置的对象构造器 Function 去 实例化 并返回一个 函数对象。 那么我们可以想象一下,是不是 我们自己在写函数的时候直接 new Function 的方法来写 会不会执行效率更高。
同样 问题就来了, var fn = new Function('a', 'return a')
这样写的话,参数还好,但是 函数体 如果很长 很多的话就很难受了,所以~
面前我们所用的创建 函数对象的方法 即为 最方便的方法。
二. 原型对象
什么是原型对象?
在 JavaScript 中,每当定义一个对象的时候,对象中都会包含一些预定义的属性
。 其中 函数对象
的一个属性就是 prototype
。 (上文介绍到的 普通对象没有 prototype,但是有 __proto__
) (但是 函数对象也有 proto 这里需要注重理解下,不然容易出错。)
所以 经过上面的解释 是不是就清楚了,原型对象
也是一种 普通对象。但是只有 函数对象
拥有。
但是 有且仅有一个特殊的 案例 需要注意下。
eg:
function f1(){}
console.log(f1.prototype) // {...}
console.log( typeof f1.prototype) // object
console.log(typeof Function.prototype) // Function
console.log(typeof Object.prototype) // Object
console.log(typeof Function.prototype.prototype) //undefined
原型对象其实就是普通对象(Function.prototype除外,它是函数对象,但它很特殊,他没有prototype属性(前面说道函数对象都有prototype属性))
为什么?
从这句console.log(f1. prototype) //f1 {}
的输出就结果可以看出,f1.prototype 就是 f1 的一个实例对象(这里结合上面讲到了 实例话 函数对象的过程)。就是在f1 函数 在创建的时候,创建了一个它的实例对象 (var temp = new Function('','') ) 并赋值给它的prototype (f1.prototype = temp)
console.log(typeof Function.prototype.prototype) //undefined
唯一一个特殊的 函数对象没有 prototype 属性的。 为什么? 就是根据上面在 控制台打印出来的结果。它是个特例,需要特殊记忆!
原型对象有什么作用?
主要是用来继承
eg:
var Person = function(name) {
this.name = name
this.getName = function() {
return this.name
}
}
Person.prototype.changeName = function(name) {
this.name = name
}
Person.prototype.getFirstName = function(name) {
return this.name
}
var zhang = new Person('zhang')
var res0 = zhang.getName()
var res1 = zhang.getFirstName()
console.log(res0) // zhang
console.log(res1,'xxx') // zhang xxx
zhang.changeName('ge')
var res2 = zhang.getName()
console.log(res2) // ge
通过这个例子我们可以看出来, 我们通过给 构造函数
的 prototype
属性添加 方法(getName)。
那么它 所有实例化出来的 函数对象都会带有这个方法(getName),同样添加属性也是一样。
那么为什么 能够 实现继承呢? 下面我们就讲到了 原型链
。
三.原型链
JS 在创建对象(不论普通对象还是函数对象)的时候,都有一个叫做__proto__
对内置属性,用于指向创建它对函数对象的原型对象 prototype
var Person = function(name) {
this.name = name
this.getName = function() {
return this.name
}
}
var zhang = new Person('zhang')
zhang.__proto__ === zhang.prototype // true
同样,zhang.prototype
对象也有 __proto__
属性,它指向创建它的函数对象(Object)的prototype
zhang.prototype.__proto__ === Object.prototype // true
继续,Object.prototype
对象也有 __proto__
属性,但它比较特殊,为null
console.log(Object.prototype.__proto__) //null
我们把这个有__proto__
串起来的直到Object.prototype.__proto__
为null
的链
叫做原型链
按照我们上面说的例子来展示下这个原型链
var Person = function(name) {
this.name = name
this.getName = function() {
return this.name
}
}
var obj = new Person('zhang')
// 其中 obj.__proto__ 指向了 Person.prototype(即为 Person 的实例)
// Person.prototype 的 __proto__ 指向了 Object.prototype
// Object.prototype 的 __proto__ 指向了 null
通过 __proto__ 串起来的直到Object.prototype.__proto__为null的链叫做原型链
四.constructor
原型对象prototype
中都有个预定义的constructor
属性,用来引用它的函数对象。这是一种循环引用
**1、Person.prototype.constructor **
ƒ (name) {
this.name = name
this.getName = function() {
return this.name
}
}
2、Function.prototype.constructor
ƒ Function() { [native code] }
**3、Object.prototype.constructor **
ƒ Object() { [native code] }
person.prototype. constructor === person // true
Function.prototype.constructor === Function // true
Object.prototype.constructor === Object // true
六.总结
最后打个比喻,虽然不是很确切,但可能对原型
的理解有些帮助
父亲(函数对象),先生了一个大儿子( prototype
),也就是你大哥,父亲给你大哥买了好多的玩具,当你出生的时候,你们之间的亲情纽带(__proto__
)会让你自然而然的拥有了你大哥的玩具。
同样,你也先生个大儿子,又给他买了好多的玩具,当你再生儿子的时候,你的小儿子会自然拥有你大儿子的所有玩具。至于他们会不会打架,这不是我们的事了。
所以说,你是从你大哥那继承的,印证了那句“长兄如父”啊!
能够对上图有所理解的话,原型 、原型链 等等都有一个很好的理解了,
当然也需要有大量的 OOP 相关的开发,才能对 JS 的 OOP 有一个 深刻的理解。