对原型及原型链的简单理解
JavaScript 的语言特点
不同于 Java 和 C++ 这些基于类的面向对象语言,JavaScript 是一种基于原型的面向对象语言,因为 JavaScript 没有类的概念。
万物皆对象?
JavaScript 中一共有 6 种主要类型: string
、 number
、 boolean
、 null
、 undefined
、 object
,常见到的关于**“JavaScript 中万物皆对象”**的说法其实是错误的, string
、 boolean
、 number
、 null
和 undefined
本身并不是对象,即使 typeof null
会返回 object
也是因为不同的对象在底层都表示为二进制,在 JavaScript 中如果二进制前三位都为 0 的话会被判断为 object
类型, null
的二进制表示是全 0,自然前三位也是 0,所以执行 typeof
时会返回“ object
”。
造成万物皆对象的错觉实际上是因为 JavaScript 中有许多特殊的对象子类型,也被称为内置对象,包括 String
、 Number
、 Boolean
、 Object
、 Function
、 Array
、 Date
、 RegExp
、 Error
。这些内置函数可以通过 new
调用来当作构造函数,引擎可以根据实际情况将 string
、 number
等字面量转换成对象。
__proto__和 prototype
在 JavaScript 中,对象是拥有属性和方法的数据。每一个对象都有一个叫做 __proto__
的内置属性,它包含对指定对象的内部原型的引用。
对象的创建基本有 3 种方法:
1、字面量创建
var foo = {
name:blackstar
}
2、构造函数
function Foo(){
this.name = blackstar
}
var foo = new Foo()
3、Object.create()
var foo1 = {
name:blackstar
}
var foo2 = Object.create(foo1)
补充一个小 Tips: var foo = {}
其实是 var foo = new Object()
的语法糖。相似的, function Foo(){}
是 var Foo = new Function()
的语法糖、 var arr = []
是 var arr = new Array()
的语法糖。
所以前两种方法都是借助了构造函数创建对象,这种方式创建的对象 __proto__
属性指向其构造函数的 prototype
方法。每一个函数都有一个 prototype
(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。要注意:只有函数对象才有 prototype,普通对象并没有。
说得再多可能都没有一个例子来得更直观易懂,来吧!
function Foo(){
this.name = 'blackstar'
}
var foo = new Foo()
console.log(foo.__proto__)
console.log(Foo.prototype)
console.log(foo.__proto__===Foo.prototype)
通过构造函数 Foo 我们创建了一个对象 foo,让我们到控制台里去看一看它的 __proto__
指向什么?
确实对象 foo 的 __proto__
指向了其构造函数 Foo 的 prototype
,Foo.prototype 就是 foo 的原型对象。
构造函数的 prototype 中有一个 constructor 属性指回到该构造函数,实例对象也有一个 constructor 属性指向创建它的构造函数。
Foo.prototype.constructor === Foo
foo.constructor === Foo
大部分情况 foo.__proto__ === foo.constructor.prototype
(不包括 Object.create()
创建的) ,因为 Object.create()
的作用是创建一个具有指定原型且可选择性地包含指定属性的对象,它所创建的对象的 __proto__
指向的是这个指定原型。
这其中指向来指向去的关系结合图示去理解会更容易一些:
有一个很有趣的事情, Object.__proto__===Function.prototype
,这是因为 Object 的构造函数是 Function。
原型链
使用原型对象的好处是可以让所有的对象实例共享它所包含的属性和方法,也就是说可以不必在构造函数中定义对象实例的信息,而是直接将它们添加到原型对象中。js 引擎查找对象属性时先查找对象本身是否存在,如果没有就去它的原型对象上查找,由于所有的对象都有 __proto__
属性,这就形成了一个链条,也就是原型链。
改造一下上面的例子:
function Foo(){
this.name = 'blackstar'
}
Foo.prototype.age = 25
var foo = new Foo()
console.log(foo.age)
我们为 foo 的原型对象添加 age 这个属性,寻找 age 属性时由于 foo 自身并没有,于是向上查找它的原型对象,诶,真好 Foo.prototype
上有,完毕。
这个原型链如下图所示,原型链的顶端是 Object.prototype
,而 __proto__
最终指向 null
。
原型链继承
原型对象可以让所有的对象实例共享它所包含的属性和方法,利用这一特点我们可以实现继承。
function Cat(name){
this.name = name
}
function Animal(){
this.eat = function(){
console.log('eat food')
}
}
Cat.prototype = new Animal()
var miao = new Cat('miao')
console.log(miao.eat())
通过将 miao 的原型对象指向 Animal 便继承了原本自身没有的 eat 方法。关于继承,这里只是引申一个简单的例子,更详细的值得另开篇章讨论,这里暂且不细说。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Vue 组件通信
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论