从数据类型讲原型原型链

发布时间:2019-08-14 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了从数据类型讲原型原型链脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

系列文章

1 、js数据类型--object

一、数据类型

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

传送门:更多symbol的用法可以看阮一峰ECMAScript 6 入门

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

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"

二、对象类型

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

1、本地对象

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

  本地对象包含但不限于Object、Function、ArrayString、Boolean、NumberDate、RegExp、各种错误类对象(Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError)

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

2、内置对象

  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、ArrayString、Boolean、NumberDate、RegExp、Error、Math、Global,ES6中出现的Set 、MapPromise、Proxy应该也算是比较常用的),这是不严谨的,JavaScript中本地对象、内置对象和宿主对象一文中,把本地对象、内置对象统称为“内部对象”,算是比较贴切的。

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

3、宿主对象

由ECMAScript实现的宿主环境提供的对象,可以理解为:浏览器提供的对象。所有的BOM和DOM都是宿主对象。

4、自定义对象

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

5、判断对象的类型

对象的类型不能使用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]"
@H_126_295@三、构造函数

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

1、对象的创建

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

生成一个函数可以通过function关键字:

function a(){
    console.LOG(1)
}
//或者
var b = function(){
    console.log(2)
}

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

  JavaScript 语言中,生成实例对象的传统方法是通过构造函数,即我们通过函数来创建对象,这也证明了函数在JavaScript中具有非常重要的地位,因此说函数是一等公民。

2、构造函数创建对象

  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]"

3、构造函数和普通函数

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

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

四、原型与原型链

1、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对象。

从数据类型讲原型原型链

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

  比如上例中,调用p.a,对象p上找不到a属性,就会去找p.__proto__.a,p.__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}

2、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中内容。

从数据类型讲原型原型链

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

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

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

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

从数据类型讲原型原型链

  经年流转,函数先祖发挥特长,制造出了一系列的函数,如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"); 

从数据类型讲原型原型链

  可以看到,其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

3、自定义构造函数创建对象

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

  这里我们不对对象的创建方式多做讨论,仅以构造函数为例

  当我们使用字面量创建一个对象的时候,其父对象默认为对象先祖,也就是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

从数据类型讲原型原型链

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

  最后希望大家可以在javascript的大道上肆意驰骋。

其他好文

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

脚本宝典总结

以上是脚本宝典为你收集整理的从数据类型讲原型原型链全部内容,希望文章能够帮你解决从数据类型讲原型原型链所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。