在之前的《JavaScript对象基础》中,我们大概了解了对象的创建和使用,知道对象可以使用构造函数和字面量方式创建。那么今天,我们就一起来深入了解一下JavaScript中的构造函数以及对象的原型及原型链。

一     构造函数

  1,什么是构造函数

Javascript中使用构造函数的目的是批量创建拥有相同属性的不同对象

实际上构造函数和普通函数并没有本质上的区别,唯一不同的地方在于:构造函数一般使用new关键字调用。

为了容易区别哪些是普通函数,哪些是构造函数,业界的共识是:构造函数使用大驼峰式命名规则(所有单词首字母大写)。普通函数和变量采用小驼峰式命名规则。

  1. function myFunction(){
  2. //这是普通函数
  3. }
  4. function MyFunction(){
  5. //这是构造函数
  6. }

  

  2,构造函数的特点

构造函数最大的特点就是通过关键字this来给即将创建的对象添加属性和方法。

  1. function Person(){
  2. this.name = 'ren';
  3. this.age = 12;
  4. }
  5. var perseon = new Person();
  6. console.log(person);//{name:'ren',age:12}

  

  3,构造函数的执行过程

首先隐式的创建一个空对象,赋值给this。

然后通过this添加属性和方法。

最后隐式的返回this对象。

  1. //执行过程,以上面的Person为例
  2. var this = {};
  3. this.name = 'ren';
  4. this.age = 12;
  5. return this;

  4,构造函数的返回值

上面提到,构造函数执行到最后将隐式的的返回一个对象,但相信大家也没有忘记,普通函数可以使用return关键字指定返回值。那么,如果我们手动的在构造函数最后添加了return关键字,那么它究竟会返回什么呢?

默认返回this。

如果手动添加原始值,还是返回this。

  1. function Person(){
  2. this.name = 'ren';
  3. this.age = 12;
  4. return 50;
  5. }
  6. var person = new Person();
  7. console.log(person);//{name:'ren',age:12}

手动添加引用值,最终返回这个引用值。

  1. function Person(){
  2. this.name = 'ren';
  3. this.age = 12;
  4. return {name:'ru',age:22};
  5. }
  6. var person = new Person();
  7. console.log(person);//{name:'ru',age:22}

  请注意,以上情况都是基于把它当做构造函数,使用new关键字调用的结果。如果把它当做普通函数执行,那么无论return后面添加什么值,都将原样返回,如果没有return,则只会返回undefined。并且,这时函数内部将不会创建一个空对象,而且this也将不再引用这个空对象了,而是指向window对象。

  1. function Person(){
  2. this.name = 'ren';
  3. return {name:'person'};
  4. }
  5. function Animal(){
  6. this.name = 'ren';
  7. return 'dog';
  8. }
  9. var person = Person();
  10. var animal = Animal();
  11. console.log(person);//{name:'person'}
  12. console.log(animal);//'dog'

二     函数的原型

  

  一般我们在讨论原型的时候通常是指构造函数的原型,因为使用普通函数的原型没有实际意义。所以下面提到的”原型”或“函数的原型”均指构造函数的原型。

  要理解函数的原型其实很简单。只需弄清楚3个属性:

fn.prototype

obj.__proto__

obj.constructor

  1,  函数的prototype属性

  prototype是函数才具有的属性,它指向一个对象,该对象具有的属性和方法将被构造函数创建的对象(实例)继承。

  1. function test(){}
  2. console.log(typeof test.prototype);//'object'
  3. console.log(test.prototype);//{...}

  这里说继承其实并不准确。比如a继承了b的属性和方法。字面上的意思是:a拥有了和b完全相同的属性和方法。但构造函数创建的对象(实例)并没有直接拥有原型上的属性和方法,它只是拿到了使用那些属性和方法的权限而已

  1. function Person(){}
  2. Person.prototype.name = 'ren';
  3. var person = new Person();
  4. console.log(person.hasOwnProperty('name'));//false,hasOwnProperty()方法用于检测对象真实具有某属性,而非继承
  1. console.log(person.name);//'ren',但是可以访问name属性

  2,  对象的__proto__属性

  大部分对象(不管什么方式创建的)都有__proto__属性。这个属性将指向它自己的原型。那么它自己的原型是个什么东西呢?

  前面提到,构造函数的目的是批量创建拥有相同属性的不同对象,既然要创建大量拥有相同属性的对象,那么肯定需要一个统一的模板,这个统一的模板就是对象的原型。实际上它就是构造函数的prototype属性指向的那个对象。

  1. function Person(){}
  2. var person = new Person();
  3. console.log(person.__proto__ === Person.prototype);//true

  

  3,  对象的constructor属性

  constructor属性并不是所有对象都有的,只有原型对象才具有该属性。该属性指向与之相关联的构造函数。

  1. function Person(){}
  2. var person = new Person();
  3. console.log(person.hasOwnProperty('constructor'));//false
  4. console.log(Person.prototype.constructor === Person);//true
  5. console.log(person.__proto__.constructor === Person);//true

  注意,虽然constructor属性并不是所有对象都有的,但是实例依然可以访问该属性,并最终得到相应的构造函数。那是因为当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性。

  4,  实例、构造函数和原型的关系

三     原型链

  

  1,原型链

  前面讲到,大部分对象都有__proto__属性,指向它自己的原型对象。那么原型对象自身呢?原型对象自身作为对象,当然也具有__proto__属性,并且指向原型的原型。

  同样的,原型的原型也是一个对象,那么它也就有一个constructor属性指向一个关联的构造函数。依次类推,原型对象最终将指向Object对象的原型,与之相关联的构造函数则是Object。并且Object对象的原型就没有原型对象了,如果访问Object.prototype.__proto__将返回null。

  1. function Father(){}
  2. Father.prototype.name = 'ren';
  3. var father = new Father();
  4.  
  5. function Son(){}
  6. Son.prototype = father;
  7. Son.prototype.age = 12;
  8. var son = new Son();
  9.  
  10. function GrandSon(){}
  11. GrandSon.prototype = son;
  12. GrandSon.prototype.address = 'cd';
  13. var grandson = new GrandSon();
  14.  
  15. Object.prototype.mail = '@';
  16.  
  17. console.log(grandson.name);//'ren'
  18. console.log(grandson.age);//
  19. console.log(grandson.address);//'cd'
  20. console.log(grandson.mail);//'@'

  从上面的例子可以看出,访问对象的属性和方法,其实是通过__proto__属性在对象的原型链上查找,这一点和在函数内访问变量有一点类似。

  其实上面的例子有一个小小的bug,不知道你们发现了没有。

  1. console.log(Son.prototype.constructor);//Father (){}
  2. console.log(GrandSon.prototype.constructor);//Father (){}
  3. //打印的都是构造函数Father,这是为什么呢?

  Son构造函数的原型被我们手动指定为了father(Father构造函数的一个实例),father作为一个实例,它并不真实拥有constructor属性,所以当我们访问Son.prototype.constructor属性时,实际在访问father.__proto__.constructor,即Father构造函数。

  但Son作为一个构造函数,被它构造出来的对象在访问constructor时理应指向Son本身才符合逻辑,所以我们应该在修改Son的原型后手动为father实例添加constructor属性,并引用Son。

  相似的,son实例也应添加一个constructor属性,并引用GrandSon(使用father或Son.prototype都可以达到目的,因为他们指向同一个对象。同理,son或者GrandSon.prototype也一样)。

  1. Son.prototype.constructor = Son;
  2. GrandSon.prototype.constructor = GrandSon;

  这一段可能理解起来有点绕,但是请务必多尝试,并真正理解它。原型链是JS中一个相当重要的概念。

  如果不适用ES6的语法,这就是使用ES5语法实现的继承,它还有另一个响亮的名字:圣杯模式。

  最后附一张原型链的图解。

  2,Object.create()

方法接受一个对象或者null作为参数,返回一个对象。如果传递了一个对象,那么该对象将成为返回对象的原型。

  1. var myProto = {name:“ren”};
  2. var obj = Object.create(myProto);
  3. obj.__proto__ === myProto;//true

  如果传递了null作为参数,那么它将没有原型,所以也没有__proto__属性了。这就是在第2.2节说大部分对象都有__proto__属性的原因了。还有一种说法是:并不是所有对象最终都继承自Object。当然null也不继承自Object。

  1. var obj = Object.create(null);
  2. console.log(obj.__proto__);//undefined

JavaScript之深入对象(一)的更多相关文章

  1. javascript是判断对象是否是数组

    JS中的数据类型: 2大类 原始类型:值保存在变量本地的数据类型 5种:Number String Boolean undefined null Number:8bytes 舍入误差-->四舍五 ...

  2. JavaScript 基础回顾——对象

    JavaScript是基于对象的解释性语言,全部数据都是对象.在 JavaScript 中并没有 class 的概念,但是可以通过对象和类的模拟来实现面向对象编程. 1.对象 在JavaScript中 ...

  3. javascript中的对象,原型,原型链和面向对象

    一.javascript中的属性.方法 1.首先,关于javascript中的函数/“方法”,说明两点: 1)如果访问的对象属性是一个函数,有些开发者容易认为该函数属于这个对象,因此把“属性访问”叫做 ...

  4. Javascript 中判断对象为空

    发现了一个巧妙的实现: 需要检查一个对象(Object)是否为空,即不包含任何元素.Javascript 中的对象就是一个字典,其中包含了一系列的键值对(Key Value Pair).检查一个对象是 ...

  5. JavaScript 中的对象

    JavaScript 中的对象 在 JavaScript 中,对象是数据(变量),拥有属性和方法. JavaScript 中的所有事物都是对象:字符串.数字.数组.日期,等等.   访问对象的属性 访 ...

  6. 使用JavaScript的history对象来实现页面前进后退(go/back/forward)。

    我们都知道JavaScript有history对象,主要是用来记录浏览器窗口的浏览记录.但是,JS脚本是不允许访问到这个记录里面的内容(隐私). 常见的用法是: history.back();//返回 ...

  7. javascript类型系统——Math对象

    × 目录 [1]常量 [2]函数 前面的话 javascript使用算术运算符实现基本的算术运算,如果要实现更加复杂的算术运算,需要通过Math对象定义的常量和函数来实现.和其他对象不同,Math只是 ...

  8. javascript中Date对象的应用——简易日历的实现

    × 目录 [1]效果 [2]HTML [3]CSS[4]JS 前面的话 简易日历作为javascript中Date对象的常见应用,用途较广泛.本文将详细说明简易日历的实现思路 效果演示 HTML说明 ...

  9. JavaScript学习04 对象

    JavaScript学习04 对象 默认对象 日期对象Date, 格式:日期对象名称=new Date([日期参数]) 日期参数: 1.省略(最常用): 2.英文-数值格式:月 日,公元年 [时:分: ...

  10. JavaScript学习笔记——对象知识点

    javascript对象的遍历.内存分布和封装特性 一.javascript对象遍历 1.javascript属性访问 对象.属性 对象[属性] //字符串格式 //javascript属性的访问方法 ...

随机推荐

  1. 记录一则AIX使用裸设备安装OracleRAC的问题

    需求背景:在AIX6.1上安装Oracle 10g RAC,一线工程师反馈节点2运行root脚本无法成功,跟进排查发现实际上底层存储磁盘的准备工作就存在问题. 客户要求底层存储选用裸设备方式,所以必须 ...

  2. JMeter用户参数

    压测的时候,经常需要使用动态参数,即每次请求的参数不一样,这样才能更好的模拟真实的用户请求,我们首先能想到的也是最常见的就是用户ID 1.  用户参数 我们可以定义一个变量,然后指定多个值,这样每次请 ...

  3. VScode 插件推荐与C/C++配置

    以下是我经常用到的VScode插件.由于插件本身具有详细的配置和介绍,不对插件本身的安装配置进行说明,仅仅支出这些插件的主要功能.具体使用强烈推荐看一下安装插件后的说明,大多数的问题和设置都可以找到, ...

  4. 奶牛跟蜗牛,哪种动物智商更高?——T检验帮你找到答案

    奶牛跟蜗牛,都是“牛”,那么哪种动物更“牛”,智商更高呢?此时就能用到T检验来找答案~   T 检验(独立样本 T 检验),用于分析定类数据与定量数据之间的关系情况.例如,在本研究中,我们想探究奶牛跟 ...

  5. 【故障公告】升级阿里云 RDS SQL Server 实例故障经过

    昨天晚上,我们使用的阿里云 RDS SQL Server 2008 R2 实例突然出现持续 CPU 100% 问题,后来我们通过重启实例恢复了正常(详见故障公告).但是在恢复正常后发现了新问题,这台 ...

  6. hdu 4825 Xor Sum(01字典树模版题)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4825 题解:一到01字典树的模版题,01字典树就是就是将一些树用二进制放到一个树上这样可以方便对整体异 ...

  7. poj1986 Distance Queries(lca又是一道模版题)

    题目链接:http://poj.org/problem?id=1986 题意:就是老问题求val[u]+val[v]-2*val[root]就行.还有这题没有给出不联通怎么输出那么题目给出的数据一定 ...

  8. spark与mapreduce的区别

    spark是通过借鉴Hadoop mapreduce发展而来,继承了其分布式并行计算的优点,并改进了mapreduce明显的缺陷,具体表现在以下几方面: 1.spark把中间计算结果存放在内存中,减少 ...

  9. 封装返回给前台的json

    JsonResult类的定义: /*生成Json串*/ public class JsonResult { /** * 返回错误状态的json串 * * @param status 错误代码,非零数值 ...

  10. mysql 复制表结构和表数据

    CREATE TABLE a1 ( id INT NOT NULL AUTO_INCREMENT COMMENT '编号', txt VARCHAR(20) NOT NULL DEFAULT '' C ...