只有对象,没有类;对象继承对象,而不是类继承类。 javascript
“原型对象”是核心概念。原型对象是新对象的模板,它将自身的属性共享给新对象。一个对象不但能够享有本身建立时和运行时定义的属性,并且能够享有原型对象的属性。 html
每个对象都有本身的原型对象,全部对象构成一个树状的层级系统。root节点的顶层对象是一个语言原生的对象,只有它没有原型对象,其余全部对象都直接或间接继承它的属性。 java
使用”原型对象”做为”模板”生成新对象 :这个步骤是必要的,这是每一个对象出生的惟一方式。以原型为模板建立对象,这也是”原型”(prototype)的原意。 node
初始化内部属性 :这一步骤不是必要的。通俗点说,就是,对”复制品”不满意,咱们能够”再加工”,使之得到不一样于”模板”的”个性”。 web
因此在JavaScript的世界里,万物皆对象这个概念从一而终。typescript
全局对象:通常全局对象会有两个,一个是ecma提供的Global对象,一个是宿主提供。如在浏览器中是window、在nodejs中是global。【因此啊,在浏览器中全局对象Global+window】浏览器
一般状况下ecma提供的Global对象对是不存在的,没有具体的对象框架
宿主对象-host object:即由 ECMAScript 实现的宿主环境提供的对象,包含两大类,一个是宿主提供,一个是自定义类对象,ECMAScript官方未定义的对象都属于宿主对象,全部非本地对象都是宿主对象。宿主提供对象原理--->由宿主框架经过某种机制注册到ECscript引擎中的对象,如宿主浏览器(以远景为参考)会向ECscript注入window对象,构建其实现javascript。ide
内置对象-Build-in object:由 ECMAScript 实现提供的、独立于宿主环境的全部对象,在 ECMAScript 程序开始执行时出现,即在引擎初始化阶段就被建立好的对象。这意味着开发者没必要明确实例化内置对象,它已被实例化了Global(全局对象)、Math、JSON函数
基本包装类型对象:ECMAScript还提供了3个特殊的引用类型: Boolean、Number、String。这些类型与其余内置对象类型类似,但同时具备各自的基本类型相应的特殊行为。实际上,每当读取一个基本类型值得时候,后台就会建立一个对应的基本包装类型的对象,从而让咱们可以调用一些方法来操做这些数据。 包装类型,是一个专门封装原始类型的值,并提供对原始类型的值执行操做的API对象
其余内置对象与基本包装类型对象的区别?
普通的内置对象与基本包装类型的主要区别就是对象的生命期,使用new操做符建立的引用类型的实例,在执行流离开当前做用域以前都一直保存在内存中,而自动建立的基本包装类型的对象,则只是存在于一行代码的执行瞬间,而后当即被当即销毁。这意味着咱们不能再运行时为基本包装类型值添加属性和方法。
var s1="some text"; s1.color="red"; var s2=new String("some text"); s2.color="red"; console.log(s1.color);//undefined console.log(s2.color);//red console.log(s1==s2);//true console.log(s1===s2);//false
在第二行为s1添加一个color属性,第三行代码执行时,再次访问s1,结果s1的color属性被销毁了。详情推荐阅读《JavaScript内置对象--基本包装类型(Boolean、Number、String)详解》
原生对象-native object:也叫内部对象、本地对象。独立于宿主环境的ECMAScript实现提供的对象。与宿主无关,在javascript(远景浏览器)、nodejs(node平台)、jscript(ie浏览器)、typescript(微软平台)等等中均有这些对象。简单来讲,本地对象就是 ECMA-262 定义的类(引用类型)。
Object、Function、Array、String、Boolean、Number、Date、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError、Global
在运行过程当中动态建立的对象,须要new,以Number为例:
var n1=Number('1'); var n2=1; n2.xxx=2;console.log(n2); //undefined console.log(n1===n2)//false console.log(n1.toString()===n2.toString())//true console.log(n1.__proto__===Number)//false console.log(n2.__proto__===Number)//false console.log(n1.__proto__===n2.__proto__)//true
n1和n2虽然数值都是1,但n2的类型属于'object',n1则为'number',身为基本类型number的n1直接指向了数字1,而n2指向了一个地址,这个地址中存放了数值1,这就是对象和基本类型的区别。
可是原型对象只存在于函数对象。也就是本质上只要是经过new Function建立的函数对象会有一个原型对象。
而对于其余非Function的引用类型归根结底也是经过new Function建立的。
如上面提到的Array类型、Object类型。
实际上,在每一个函数对象建立的时候,都会自带一个prototype的属性,这个属性至关于一个指针,指向他自己的原型对象,这个原型对象里包含着自定义的方法属性,
function a(){ this.name='xiaoming'; this.sayName=function () { console.log(this.name); } }
在默认状况下,a.prototype下会带有一个constructor属性,这个属性指向建立当前函数对象的构造函数,好比这里
constructor指向构造函数a自己也就是说:a.prototypr.constructor==a //true
另外默认还有一个_proto_属性,这个属性指向由建立这个函数对象的引用类型中继承而来的属性和方法。
当经过构造函数实例化一个对象b时,即new a();
首先这个new出来的对象属于普通对象,因此没有prototype属性。但他有_proto_这个属性,这个属性指向建立它的引用类型的原型对象,在这个例子中指向a.prototype,从而继承来自引用类型a的属性和方法。推荐阅读《JS 的 new 究竟是干什么的?》
var 对象 = new 函数对象 这个声明形式能够引伸出:
函数.__proto__ ===Function.prototype Function.__proto__ === Function.prototype Object.__proto__ === Function.prototype //Objec也是个函数,函数都是由Function构造出来的。 Number.__proto__ === Function.prototype 构造函数.prototype.__proto__ ===Object.prototype Function.prototype.__proto__ ===Object.prototype Number.prototype.__proto__ ===Object.prototype Object.__proto__ .__proto__ ===null
理解了以上的关系后,'__proto__'是对象的属性、'prototype'是函数的属性这句话也就懂了
null是对象原型链的终点,其值既有(是一个对象)又无(不引用任何对象),表明着对象本源的一种混沌、虚无的状态,正与老子《道德经》中的“道”,有着同等的意义(心中一万只艹尼玛奔腾而过,仍是写java爽啊)。好比:《undefined与null的区别》
在JS中,undefined是全局对象的一个属性,它的初始值就是原始数据类型undefined,而且没法被配置,也没法被改变。undefined从字面意思上理解为“未定义”,即表示一个变量没有定义其值。
而null是一个JS字面量,表示空值,即没有对象。与undefined相比,null被认为是“指望一个对象,可是不引用任何对象的值”,而undefined是纯粹的“没有值”。
// null为对象原型链的终点 console.log(Object.getPrototypeOf(Object.prototype)); // null // null是一个对象 console.log(typeof null); // object // null 为空 console.log(!null); // true
JS中的全部事物都是对象,对象是拥有属性和方法的数据。
为了描述这些事物,JS便有了“原型(prototype)”的概念。
原型模式是js对继承的一种实现:使用原型,能复用代码,节省内存空间 (java类的代码在内存只有一份,而后每一个对象执行方法都是引用类的代码,全部子类对象调用父类方法的时候,执行的代码都是同一份父类的方法代码。可是JS没有类,属性和方法都是存在对象之中,根本没有办法作到java那样经过类把代码共享给全部对象)。
推荐阅读《深入理解JavaScript基于原型的面向对象》
从一张图看懂原型对象、构造函数、实例对象之间的关系
prototype:构造函数中的属性,指向该构造函数的原型对象。
constructor:原型对象中的属性,指向该原型对象的构造函数
_proto_:实例中的属性,指向new这个实例的构造函数的原型对象
在JavaScript 中,每一个对象都有一个指向它的原型(prototype)对象的内部连接。这个原型对象又有本身的原型,直到某个对象的原型为 null 为止(也就是再也不有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链(prototype chain)。
要清楚原型链,首先要弄清楚对象
普通对象
最普通的对象:有__proto__属性(指向其原型链),没有prototype属性。
原型对象(Person.prototype 原型对象还有constructor属性(指向构造函数对象))
函数对象:
凡是经过new Function()建立的都是函数对象。
拥有__proto__、prototype属性(指向原型对象)。
JavaScript 对象是动态的属性“包”(指其本身的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不只仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依此层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
显式原型(explicit prototype property )每个函数在建立以后都会拥有一个名为prototype的属性,这个属性指向函数的原型对象。用来实现基于原型的继承与属性的共享。
隐式原型 (implicit prototype link) JS中任意对象都有一个内置属性__proto__(部分浏览器为[[prototype]]),指向建立这个对象的函数(即构造函数)constructor的prototype。用来构成原型链,一样用于实现基于原型的继承。
当咱们「读取」 obj.toString 时,JS 引擎会作下面的事情:
1. 看看 obj 对象自己有没有 toString 属性。没有就走到下一步。
2. 看看 obj.__proto__ 对象有没有 toString 属性,发现 obj.__proto__ 有 toString 属性,因而找到了
3. 若是 obj.__proto__ 没有,那么浏览器会继续查看 obj.__proto__.__proto__,若是 obj.__proto__.__proto__ 也没有,那么浏览器会继续查,obj.__proto__.__proto__.proto__
直到找到 toString 或者 __proto__ 为 null(无论你从那个属性开始,连续引用__proto__的指针,最后输出的那个值就是null)。
上面的过程,就是「读」属性的「搜索过程」。
而这个「搜索过程」,是连着由 __proto__ 组成的链子一直走的。
这个链子,就叫作「原型链」。
要搞清楚 valueOf / toString / constructor 是怎么来的,就要用到 console.dir 了。
共享原型链(Shared prototype chain)此模式全部子对象及后代对象都共享一个原型(都是经过b.prototype=a.prototype;这种模式链接的对象),在这些后代对象上修改原型,会影响因此处在同一共享原型链上的全部对象。并且此模式只继承原型链上的属性和方法,经过this定义的属性和方法没法访问和继承
那么 obj.toString 和 obj2.toString 实际上是同一个东西,也就是 obj2.__proto__.toString。
这有什么意义呢?
若是咱们改写 obj2.__proto__.toString,那么 obj.toString 其实也会变!
这样 obj 和 obj2 就是具备某些相同行为的对象,这就是意义所在。
若是咱们想让 obj.toString 和 obj2.toString 的行为不一样怎么作呢?
直接赋值就行了:
obj.toString = function(){ return '新的 toString 方法' }
每建立一个函数都会有一个prototype属性,这个属性是一个指针,指向一个对象(经过该构造函数建立实例对象的原型对象)。原型对象是包含特定类型的全部实例共享的属性和方法。原型对象的好处是,能够让全部实例对象共享它所包含的属性和方法。
原型对象属于普通对象。Function.prototype是个例外,它是原型对象,却又是函数对象,做为一个函数对象,它又没有prototype属性。
拥有了描述事物的能力,却没有创造事物的能力,显然是不完整的,所以须要一个Object的生成器来进行对象的生成。
JS将生成器以构造函数constructor来表示,构造函数是一个指针,指向了一个函数。
函数(function) 函数是指一段在一块儿的、能够作某一件事的程序。构造函数是一种建立对象时使用的特殊函数。
对象的构造函数function Object同时也是一个对象,所以须要一个可以描述该对象的原型,该原型即是Function.prototype,函数的原型用来描述全部的函数。对象的构造函数的__proto__指向该原型。
函数的原型自己也是对象,所以其__proto__指向了对象的原型。一样,该对象也须要一个对应的生成器,即其构造函数function Function。
函数的构造函数是由函数生成的一个对象,因此其原型即为函数的原型,其隐式原型也一样为函数的原型Function.prototype。
instanceof操做符的内部实现机制和隐式原型、显式原型有直接的关系。instanceof的左值通常是一个对象,右值通常是一个构造函数,用来判断左值是不是右值的实例。它的实现原理是沿着左值的__proto__一直寻找到原型链的末端,直到其等于右值的prototype为止。
instanceof 的做用是判断一个对象是否是一个函数的实例。好比 obj instanceof fn, 其实是判断fn的prototype是否是在obj的原型链上。因此
instanceof运算符的实质:用来检测 constructor.prototype是否存在于参数 object的原型链上。
根据上图展现的Object和Function的继承依赖关系,咱们能够经过instanceof操做符来看一下Object和Function的关系:
console.log(Object instanceof Object); // true console.log(Object instanceof Function); // true console.log(Function instanceof Object); // true console.log(Function instanceof Function); // true
函数与对象相互依存,分别定义了事物的描述方法和事物的生成方法,在生成JS万物的过程当中缺一不可。
Function instanceof Function // true, why? Function.prototype是原型对象,倒是函数对象
Object特殊在Object.prototype是凭空出来的。语法上,全部的{}都会被解释为new Object();
Function特殊在__proto__ == prototype。语法上,全部的函数声明都会被解释为new Function()。
咱们来看Function和Object的特殊之处:
Object是由Function建立的:由于Object.__proto__ === Funciton.prototype;
同理,Function.prototype是由Object.prototype建立的;
Funciton是由Function本身建立的!
Object.prototype是凭空出来的!
推荐阅读 《JavaScript 内置对象与原型链结构》与《JavaScript中的难点之原型和原型链》
这几句话能解释一切关于原型方面的问题:
当 new 一个函数的时候会建立一个对象,『函数.prototype』 等于 『被建立对象.__proto__』
一切函数都是由 Function 这个函数建立的,因此『Function.prototype === 被建立的函数.__proto__』
一切函数的原型对象都是由 Object 这个函数建立的,因此『Object.prototype === 一切函数.prototype.__proto__』
推荐阅读:《对原型、原型链、 Function、Object 的理解》
先说一下继承,许多OO语言都支持两张继承方式:接口继承、实现继承。
|- 接口继承:只继承方法签名
|- 实现继承:继承实际的方法
因为函数没有签名,在ECMAScript中没法实现接口继承,只支持实现继承,而实现继承主要是依靠原型链来实现。
利用原型让一个引用类型继承另外一个引用类型的属性和方法。
每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数想指针(constructor),而实例对象都包含一个指向原型对象的内部指针(__proto__)。若是让原型对象等于另外一个类型的实例,此时的原型对象将包含一个指向另外一个原型的指针(__proto__),另外一个原型也包含着一个指向另外一个构造函数的指针(constructor)。假如另外一个原型又是另外一个类型的实例……这就构成了实例与原型的链条。
原型链基本思路(图解):
推荐阅读《JS重点整理之JS原型链完全搞清楚》
类(Class)是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础。类是一种用户定义类型,也称类类型。每一个类包含数听说明和一组操做数据或传递消息的函数。类的实例称为对象。
在ECMAScript 2015 中引入的JS类(classes)以前,要在JS中实现类即是采用原型继承的方式。
当把一个函数做为构造函数,使用new关键字来建立对象时,即可以把该函数看做是一个类,建立出来的对象则是该类的实例,其隐式原型__proto__指向的是该构造函数的原型。
在访问该对象的属性或方法时,JS会先搜索该对象中是否认义了该属性或方法,若没有定义,则会回溯到其__proto__指向的原型对象去搜索,若仍然未搜索到,则会继续回溯该原型的原型,直到搜索到原型链的终点null;
这种特性能够理解为:构造函数生成的实例,继承于该构造函数的原型。
得益于这种特性,咱们可使用定义构造函数的方式来定义类。
function Person() {} // 定义Person构造函数 // 一般以大写字母开头来定义类名 console.log(new Person() instanceof Person); // true
以上定义了Person类,该构造函数是由Function构造而来,因此其隐式原型指向函数的原型,而为了描述该事物,同时生成了该类的原型Person.prototype,该原型又是由Object构造而来,因此其隐式原型指向了对象的原型。
后记:文字有点乱,就是多篇文章的精华提炼。发现把一个本身懂的事情,深刻浅出讲明白,并不是易事。文有不妥之处,请留言告知,谢谢。
文章首发于:https://www.zhoulujun.cn/html/webfront/ECMAScript/js/2015_0715_119.html,若是不妥之处,请到官网留言,谢谢!
参考文字: