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 有一个 深刻的理解.
Github 地址, 欢迎 Star https://github.com/erbing/blog/blob/master/%E5%89%8D%E7%AB%AF%08%E4%B9%8B%E8%B7%AF/05%EF%BC%9AJS%20%E5%8E%9F%E5%9E%8B%E9%93%BE.md
来源: https://www.cnblogs.com/erbingbing/p/9342365.html