从js数据类型到原型原型链

#系列文章javascript

1 、js数据类型--objecthtml

1、数据类型

  在JavaScript中,数据类型能够分为原始类型以及引用类型。其中原始类型包括string,number, boolean, null, undefined, symbol(ES6新增,表示独一无二的值),这6种数据类型是按照值进行分配的,是存放在栈(stack)内存中的简单数据段,能够直接访问,数据大小肯定,内存空间大小能够分配。引用类型包括function,object,array等能够可使用new建立的数据,又叫对象类型,他们是存放在堆(heap)内存中的数据,如var a = {},变量a实际保存的是一个指针,这个指针指向对内存中的数据 {}java

传送门:更多symbol的用法能够看阮一峰ECMAScript 6 入门es6

  讲到数据,那不得不讲的就是变量,JavaScript中的变量具备动态类型这一特性,这意味着相同的变量可用做不一样的类型:web

var x;            // x 为 undefined
x = 6;            // x 为 number
x = "hfhan";      // x 为 string
复制代码

  JavaScript中能够用typeof 操做符来检测一个数据的数据类型,可是须要注意的是typeof null结果是object, 这是个历史遗留bug:数组

typeof 123;               // "number"
typeof "hfhan";           // "string"
typeof true;              // "boolean"
typeof null;              // "object"  独一份的不同凡响
typeof undefined;         // "undefined"
typeof Symbol("hfhan");   // "symbol"
typeof function(){};      // "function"
typeof {};                // "object"
复制代码

2、对象类型

先理解下什么是宿主环境:由web浏览器或是桌面应用系统造就的js引擎执行的环境即宿主环境。promise

一、本地对象

  ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript 实现提供的对象”。浏览器

  本地对象包含但不限于Object、Function、Array、String、Boolean、Number、Date、RegExp、各类错误类对象(Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError)bash

  注意:这里的Object、Function、Array等不是指构造函数,而是指对象的类型app

二、内置对象

  ECMA-262 把内置对象(built-in object)定义为“由 ECMAScript 实现提供的、独立于宿主环境的全部对象,在 ECMAScript 程序开始执行时出现”。这意味着开发者没必要明确实例化内置对象,它已被实例化了。ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每一个内置对象都是本地对象)。

  其中Global对象是ECMAScript中最特别的对象,由于实际上它根本不存在,但你们要清楚,在ECMAScript中,不存在独立的函数,全部函数都必须是某个对象的方法。相似于isNaN()、parseInt()和parseFloat()方法等,看起来都是函数,而实际上,它们都是Global对象的方法。并且Global对象的方法还不止这些。有关Global对象的具体方法和属性,感兴趣的同窗能够看一下这里:JavaScript 全局对象参考手册

  对于web浏览器而言,Global有一个代言人window,可是window并非ECMAScripta规定的内置对象,由于window对象是相对于web浏览器而言的,而js不只仅能够用在浏览器中。

  Global与window的关系能够看这里:概念区分:JavaScript中的global对象,window对象以及document对象

  能够看出,JavaScript中真正的内置对象其实只有两个:Global 和 Math,但是观看网上的文章资料,千篇一概的都在讲JavaScript的11大内置对象(不是说只有11个,而是经常使用的有11个:Object、Function、Array、String、Boolean、Number、Date、RegExp、Error、Math、Global,ES6中出现的Set 、Map、Promise、Proxy等应该也算是比较经常使用的),这是不严谨的,JavaScript中本地对象、内置对象和宿主对象一文中,把本地对象、内置对象统称为“内部对象”,算是比较贴切的。

  更多“内部对象”能够查看MDN>JavaScript>引用>内置对象 内容,或者经过浏览器控制台打印window来查找。

三、宿主对象

由ECMAScript实现的宿主环境提供的对象,能够理解为:浏览器提供的对象。全部的BOM和DOM都是宿主对象。

四、自定义对象

顾名思义,就是开发人员本身定义的对象。JavaScrip容许使用自定义对象,使JavaScript应用及功能获得扩充

五、判断对象的类型

对象的类型不能使用typeof来判断,由于除了Function外其余类型的对象所获得的结果全为"object"

typeof function(){};      // "function"
typeof {};                // "object"
typeof new RegExp;        // "object"
typeof new Date;          // "object"
typeof Math;              // "object"
typeof new Error;         // "object"复制代码

一个使用最多的检测对象类型的方法是Object.prototype.toString

Object.prototype.toString.apply(new Function);     // "[object Function]"
Object.prototype.toString.apply(new Object);       // "[object Object]"
Object.prototype.toString.apply(new Date);         // "[object Date]"
Object.prototype.toString.apply(new Array);        // "[object Array]"
Object.prototype.toString.apply(new RegExp);       // "[object RegExp]"
Object.prototype.toString.apply(new ArrayBuffer);  // "[object ArrayBuffer]"
Object.prototype.toString.apply(Math);             // "[object Math]"
Object.prototype.toString.apply(JSON);             // "[object JSON]"
var promise = new Promise(function(resolve, reject) {
    resolve();
});
Object.prototype.toString.apply(promise);          // "[object Promise]"复制代码

3、构造函数

构造函数是描述一类对象统一结构的函数——至关于图纸

一、对象的建立

  上面咱们已经知道了,JavaScript中的对象有很对种类型,好比Function、Object、Array、Date、Set等等,那么咱们如何去建立这些类型的数据?

生成一个函数能够经过function关键字:

function a(){
	console.log(1)
}
//或者
var b = function(){
	console.log(2)
}
复制代码

  此外建立一个对象(类型为Object的对象),能够经过{};建立一个数组,能够经过[];建立一个正则对象能够经过/.*/。可是那些没有特殊技巧的对象,就只能老老实实使用构造函数来建立了。

  JavaScript 语言中,生成实例对象的传统方法是经过构造函数,即咱们经过函数来建立对象,这也证实了函数在JavaScript中具备很是重要的地位,所以说函数是一等公民。

二、构造函数建立对象

  JavaScript中的对象在使用的时候,大部分都须要先进行实例化(除了已经实例化完成的Math对象以及JSON对象):

var a = new Function("console.log('a') ");  //构造函数建立Function对象
var b = new Object({a:1});                  //构造函数建立Object对象
var c = new Date();                         //构造函数建立Date对象
var d = new Set();                          //构造函数建立Set对象
var e = new Array(10);                      //构造一个初始长度为10的数组对象
复制代码

  能够看出,只要使用new关键字来实例化一个构造函数就能够建立一个对象了,JavaScript中内部对象的构造函数是浏览器已经封装好的,咱们能够直接拿过来使用。

使用构造函数建立的数据全是对象,即便用new关键字建立的数据全是对象,其中new作了4件事:

1)、先建立空对象
2)、用空对象调用构造函数,this指向正在建立的空对象 
    按照构造函数的定义,为空对象添加属性和方法
3)、将新建立对象的__proto__属性指向构造函数的prototype对象。
4)、将新建立对象的地址,保存到等号左边的变量中
复制代码

除了浏览器自己自带的构造函数,咱们还可使用一个普通的函数来建立对象:

function Person(){};
var p1 = new Person()
复制代码

  这个例子中Person就是一个普普统统的空函数,可是他依然能够做为构造函数来建立对象,咱们打印下p1的类型,能够看出使用自定义的构造函数,所建立的对象类型为Object

Object.prototype.toString.apply(p1);  // "[object Object]"
复制代码

三、构造函数和普通函数

  实际上并不存在建立构造函数的特殊语法,其与普通函数惟一的区别在于调用方法。对于任意函数,使用new操做符调用,那么它就是构造函数,又叫工厂函数;不使用new操做符调用,那么它就是普通函数。

  按照惯例,咱们约定构造函数名以大写字母开头,普通函数以小写字母开头,这样有利于显性区分两者。例如上面的new Object (),new Person ()。

4、原型与原型链

一、prototype 与 __proto

原型是指原型对象,原型对象从哪里来?

  每一个函数在被建立的时候,会同时在内存中建立一个空对象,每一个函数都有一个prototype 属性,这个属性指向这个空对象,那么这个空对象就叫作函数的原型对象,而每个原型对象中都会有一个constructor属性,指向该函数

function b(){console.log(1)};
b.prototype.constructor === b;   // true
复制代码

抽象理解:构造函数是妻子,原型对象是丈夫,prototype是找丈夫,constructor是找妻子。

手动更改函数的原型对象:

var a = {a:1};
b. prototype = a;  //更改b的原型对象为a
a. constructor;    // function Object() { [native code] }
复制代码

为何这里a. constructor不指向b函数?

  这是由于变量a所对应的对象是事先声明好的,不是跟随函数一块儿建立的,因此他没有constructor属性,这时候寻找constructor属性就会到父对象上去找,而全部对象默认都继承自Object. Prototype,因此最后找的就是Object. Prototype. Constructor,也就是Object函数。

刚才讲到了继承,继承又是怎么一回事呢?

全部对象都有一个__proto__属性,这个属性指向其父元素,也就是所继承的对象,通常为构造函数的prototype对象。

protot

  prototype 是函数独有的;__proto__是全部对象都有的,是继承的。调用一个对象的某一属性,若是该对象上没有该属性,就会去其原型链上找。

  好比上例中,调用p.a,对象p上找不到a属性,就会去找p.__proto__.ap.__proto__.a也找不到,就会去找p.__proto__.__proto__.a,依次类推,直到找到Object.prototype.a也没找到,就会返回undefined。

  原型链是由各级子对象的__proto__属性连续引用造成的结构,全部对象原型链的顶部都是Object.prototype。

  咱们知道,当子对象被实例化以后再去修改构造函数的prototype属性是不会改变子对象与原型对象的继承关系的,可是经过修改子对象的__proto__属性,咱们能够解除子对象与原型对象之间的继承关系。

var A = function(){};    // 构造函数
A.prototype = {a:1};     // 修改原型对象
var a = new A;           // 实例化子对象a,此时a继承自{a:1}
a.a                      // 1
A.prototype = {a:2}      // 更该构造函数的原型对象
a.a                      // 1   此时,a还是继承自{a:1}
a.__proto__ = {a:3}      // 修改a的原型链
a.a                      // 3   此时,a继承自{a:3}
复制代码

二、Object.prototype与Function.prototype

  一切诞生于虚无!

  上面讲了,全部对象原型链的顶部都是Object.prototype,那么Object.prototype是怎么来的,凭空造的吗?还真是!

Object.prototype.__proto__ === null;   // true
复制代码

  上面讲了,咱们能够经过修改对象的__proto__属性来更改继承关系,可是,Object.prototype__proto__属性不容许更改,这是浏览器对Object.prototype的保护措施,修改Object.prototype的__proto__属性会抛出错误。同时,Object.prototype.__proto__也只能进行取值操做,由于null 和 underfined没有对应的包装类型,所以不能调用任何方法及属性

  在控制台打印下Object.prototype.__proto__的保护属性:

Object.getOwnPropertyDescriptor(Object.prototype,"__proto__"); 
复制代码

注:保护属性及getOwnPropertyDescriptor为ES5中内容。

1

  能够看到,其numerable、configurable属性均为false,也就是Object.prototype.__proto__属性不可删除,不可修改属性特性,而且属性作了get、set的处理。

  Object.prototype与Function.prototype是原型链中最难理解也是最重要的两个对象。下面咱们用抽象的方法来理解这两个对象:

  天地伊始,万物初开,诞生了一个对象,不知其姓名,只知道他的类型为"[object Object]",他是一切对象的先祖,为初代对象,继承于虚无(null)。

  后来,又诞生了一个对象,也不知其姓名,只知道他的类型为"[object Function]",他是一切函数的先祖,继承于对象先祖,为二代对象。

Object.prototype与Function.prototyp

  经年流转,函数先祖发挥特长,制造出了一系列的函数,如Object、Function、Array、Date、String、Number等,都说龙生九子各有不一样,这些函数虽然说各个都貌美如花,神统统天,但功能上仍是有很大的区别的。

  其中最须要关注的是Object以及Function。原来函数先祖在创造Function的时候,悄悄的把Function的prototype属性指向了本身,也把本身的constructor属性指向了Function。若是说Function是函数先祖为本身创造的妻子,那么Object就是函数先祖为对象先祖创造的妻子,一样的,Object的prototype属性指向了对象先祖,对象先祖也把本身的constructor属性指向Object,表示他赞成了这门婚事。

  此后,世人都称对象先祖为Object.prototype,函数先祖为Function.prototype。

  从上能够看出,对象先祖是一开始就存在的,而不是同Object一块儿被建立的,因此手动更改Object.prototype的指向后:

Object.prototype = {a:1};    //修改Object.prototype的指向
var a = {};                  //经过字面量建立对象
a.a                          //undefined 此时a仍然继承于对象先祖
var b = new Object();        //经过new来建立对象
b.a                          //结果是???
复制代码

  这里我本来觉得会打印1,可是实际上打印的仍是undefined,而后在控制台打印下Object.prototype,发现Object.prototype仍然指向对象先祖,也就是说Object.prototype = {a:1}指向更改失败,我猜想和上面Object.prototype的__proto__属性不容许更改,缘由是同样的,是浏览器对Object.prototype的保护措施。

  在控制台打印下Object.prototype的保护属性:

Object.getOwnPropertyDescriptor(Object,"prototype"); 
复制代码

2

  能够看到,其writable、enumerable、configurable属性均为false,也就是其prototype属性不可修改,不可删除,不可修改属性特性。

  其实不光Object.prototype不能修改,Function. Prototype、String. Prototype等内部对象都不容许修。

咱们继续往下看

原型链

由于Object、Function、Array、String等都继承自Function.prototype,因此有

Object.__proto__ === Function.prototype;       // true
Function.__proto__ === Function.prototype;     // true
Array.__proto__ === Function.prototype;        // true
String.__proto__ === Function.prototype;       // true
复制代码

全部的对象都继承于Object.prototype,因此有

Function.prototype.__proto__ === Object.prototype;     // true
Array.prototype.__proto__ === Object.prototype;        // true
String.prototype.__proto__ === Object.prototype;       // true
复制代码

三、自定义构造函数建立对象

  当咱们自定义一个对象的时候,这个对象在整个原型链上的位置是怎么样的呢?

  这里咱们不对对象的建立方式多作讨论,仅以构造函数为例

  当咱们使用字面量建立一个对象的时候,其父对象默认为对象先祖,也就是Object.prototype

var a = {};
a.__proto__ === Object.prototype;  // true
复制代码

  上面讲了,自定义构造函数所建立的对象他的类型均为"[object Object]",在函数创建的时候,会在内存中同步创建一个空对象,其过程能够看做:

function F(){};  // prototype 赋值  F.prototype = {},此时{}继承于Object.prototype
复制代码

  当咱们使用构造函数建立一个对象时,会把构造函数的prototype属性赋值给子对象的__proto__属性,即:

var a = new F();   //__proto__赋值 a.__proto__ = F.prototype;
复制代码

  由于F.prototype继承于Object.prototype,因此有

a.__proto__.__proto__ === Object.prototype;  // true
复制代码

z

  综上咱们能够看出,原型链就是根据__proto__维系的由子对象-父对象的一条单向通道,不过要理解这条通道,咱们还须要理解构造对象,类,prototype,constructor等,这些都是原型链上的美丽的风景。

  最后但愿你们能够在javascript的大道上肆意驰骋。

其余好文

JavaScript 世界万物诞生记 Prototype 与 Proto 的爱恨情仇

相关文章
相关标签/搜索