前言:这是笔者学习之后自己的理解与整理。如果有错误或者疑问的地方,请大家指正,我会持续更新!  

  先梳理一下定义:

  1. 我们通常认为 object 是普通对象,function 是函数对象;
  2. Function 跟 Object 本身是 javascript 自带的函数对象;
  3. 每一个函数对象都有一个显示的 prototype 属性,它代表了对象的原型(Function.prototype 函数对象是个例外,没有 prototype 属性);
  4. 所有对象(普通对象 和 函数对象)都有一个名为 __proto__ (注意是两个下划线连在一起) 的内部隐藏属性,指向于它所对应的原型对象。原型链正是基于 __proto__ 才得以形成(原型链不是基于函数对象的属性 prototype)。
  5. Function.prototype 是一个函数对象,前面说函数对象都有一个显示的 prototype 属性,但是 Function.prototype 却没有 prototype 属性,即Function.prototype.prototype === undefined,所以 Function.prototype 函数对象是一个特例,没有 prototype 属性。
  6. 在 JavaScript 中,每个函数对象都有名为 prototype 的属性(上面提到过 Function.prototype 函数对象是个例外,没有 prototype 属性 ),用于引用原型对象。此原型对象又有名为 constructor 的属性,它反过来引用函数本身。
  7. 如何查找一个对象的 constructor,就是在该对象的原型链上寻找碰到的第一个 constructor 属性所指向的对象。
  8. 注意 Object.constructor === Function;Object 本身就是 Function 函数构造出来的。
  9. Object 虽是 Function 构造的一个函数对象,但是 Object.prototype 没有指向 Function.prototype,即 Object.prototype !== Function.prototype;
  10. 所有对象(包括函数对象)的原型链最终都指向了 Object.prototype,Object.prototype 是一个普通对象,而 Object.prototype.__proto__ === null,原型链至此结束。
        <script type="text/javascript">
var o1 = {};
var o2 =new Object(); function f1(){}
var f2 = function(){}
var f3 = new Function(); var f4 = new f1;
var f5 = new f2;
var f6 = new f3; console.log(typeof Object); //function
console.log(typeof Function); //function
//Object 和 Function ,是 javascript 自带的对象,是函数对象 console.log(typeof o1); //object
console.log(typeof o2); //object
console.log(typeof f1); //function
console.log(typeof f2); //function
console.log(typeof f3); //function
console.log(typeof f4); //object
console.log(typeof f5); //object
console.log(typeof f6); //object console.log(typeof Function.prototype); //function
console.log(typeof f1.prototype); //object console.log(Function.prototype.prototype ); //undefined
console.log(f1.prototype.prototype ); //undefined
//Function.prototype 函数对象是个例外,没有 prototype 属性;
//普通对象也没有 prototype 属性 console.log(f4 instanceof f1);//true
console.log(f4.constructor === f1);//true
console.log(f4._proto_ === f1.prototype); //false
// __proto__ (注意是两个下划线连在一起)
console.log(f4.__proto__ === f1.prototype); //true
//所有对象(普通对象 和 函数对象)都有一个名为 __proto__ 的属性,指向于它所对应的原型对象
//f4函数是由f1函数构造的,f4也就是f1的实例,所以f4的 __proto__指向f1.prototype; //原型链:
//在访问属性时,先在f4中查找,没找到就顺着 __proto__指向的原型对象查找,
//原型对象也有一个__proto__指针,又指向了另一个原型,一个接一个,就形成了原型链,原型链的最顶层是Object.prototype,它的__proto__指向null。 console.log(f1 instanceof Function);//true
console.log(f1.constructor === Function);//true
console.log(f1.__proto__ === Function.prototype); //true
// f1 函数是由 Function 构造的,f1也就是Function的实例,所以 f1 的__proto__指向Function.prototype; console.log(typeof Function.prototype); //function
console.log(typeof Object.prototype); //object console.log(Function.prototype.constructor === Function);//true
console.log(Function.prototype instanceof Object);//true console.log(Object.prototype.constructor === Object);//true
console.log(Object.prototype instanceof Object);//false console.log(Function.prototype.__proto__ === Object.prototype); //true
//Function.prototype 也是函数对象,它是由Object 构造的,所以 Function.prototype 函数对象的__proto__指向 Object.prototype; console.log(typeof Object.prototype); //object
console.log(Object.prototype.__proto__); //null
//Object.prototype 是一个普通对象,原型链至此结束 console.log(f1.__proto__.__proto__ === Object.prototype); //true console.log(Function.prototype === Object.__proto__);//true
console.log(Function.__proto__ === Object.__proto__);//true
console.log(Function.__proto__ === Function.prototype);//true
</script>

   

   

  构造函数、原型对象、实例对象之间的关系,比较绕:

  1. 构造函数有一个 prototype 属性,指向其对应的原型对象;
  2. 原型对象有一个 constructor 属性,指向其对应的构造函数;
  3. 实例对象有一个 _proto_ 属性,指向其对应的原型对象;
  4. 实例对象可以从原型对象继承属性,所以实例对象也有一个 constructor 属性,指向其对应的构造函数;

  所以,实例对象和构造函数之间没有直接关系,间接关系是实例对象可以继承原型对象的 constructor 属性。

构造函数

  如果一个函数用 new 关键字调用,这个函数就是构造函数,并且背地里会创建一个连接到该函数的 prototype 的新对象,this 指向这个新对象;

  如果构造函数没有形参,实例化的时候是可以不带()的;如  var  a = Func; 或者  var a = Func(); 两种都可以;

  同时我们在构造函数的时候有个约定(不是规范),首字母大写,以避免忘了写 new 关键字或者在普通函数前面加 new;

  new 关键字的作用就是执行一个构造函数,并返回一个对象实例。使用 new 命令,它后面的函数的函数调用和普通函数调用就不一样了,步骤如下:

  1. 创建一个空对象,作为将要返回的对象实例;
  2. 将空对象的 __proto__ 属性指向构造函数的 prototype 属性;
  3. 将构造函数内部的 this 关键字指向空对象;
  4. 执行构造函数内部的代码;

  就是说 this 指向这个新对象,构造函数内所有针对 this 的操作,都会发生在这个新对象上;

        <script type="text/javascript">
// 创建一个名为Person 的构造函数,它构造一个带有user 和age 的对象
var Person = function (user,age) {
this.user = user;
this.age = age;
}; // 构造一个Person 实例 ,并测试
var shane = new Person ('shane',25);
console.log(shane.user);//shane
console.log(shane.age);//25
</script>

  每个对象在创建时,都自动拥有一个属性 constructor,指向其构造函数;这个 constuctor 属性实际上继承自原型对象;

  虽然对象实例和构造函数之间有这样的关系,但还是建议使用 instanceof 来检测实例对象和构造函数的关系。这是因为构造函数的属性可以被修改,并不一定准确;

        <script type="text/javascript">
var Person = function (user) {
this.user = user;
};
// 构造一个Person 实例 ,并测试
var shane = new Person ('shane');
console.log(shane.user);//shane console.log(shane.constructor === Person);//true
//每个对象在创建时,都自动拥有一个属性constructor,指向其构造函数;
//这个constuctor属性实际上继承自原型对象; Person.prototype.constructor = 123; //constructor属性可以被修改
console.log(shane.constructor === Person);//false 构造函数的属性可以被修改,所以不推荐用来检测实例对象和构造函数的关系
console.log(shane instanceof Person);//true 建议使用instanceof来检测实例对象和构造函数的关系。
</script>

  

  javascript 中构造函数是不需要有返回值的,可以认为构造函数和普通函数之间的区别就是:构造函数没有 return 语句,普通函数可以有 return 语句;

  构造函数使用 this 关键字定义变量和方法,当 this 遇到 return 的时候,会发生指向不明(调用结果不明)的问题:

  1. return 返回的不是一个对象,this 还是指向实例(新对象),调用结果也还是新对象;
  2. return 返回的是一个对象,this 就指向这个返回的对象,调用结果就是这个返回的对象;
  3. return 返回的是 null,this 还是指向实例,调用结果也还是新对象;
        <script type="text/javascript">
var Person = function(){
this.user = 'shane';
return
}
var shane = new Person;
console.log(shane.user);//shane return没有返回值,this还是指向实例(新对象),调用结果也还是新对象; var Color = function(){
this.red = 'red';
return 'hello world';
}
var redColor = new Color;
console.log(redColor.red);//red return返回的是一个基本类型的字符串(原始值),this还是指向实例(新对象),调用结果也还是新对象; var Size = function(){
this.size = 'big';
return {};
}
var sizeBig = new Size;
console.log(sizeBig.size);//undefined return返回的是一个对象,this就指向这个返回的对象,调用结果就是这个返回的对象;
</script>

  使用构造函数的好处是所有由同一个构造函数实例化出来的对象,都有同样的属性和方法;

  构造函数的不足是所有方法都要在每个实例上重新创建一次,浪费内存空间;

  可以把方法定义到构造函数外部来解决,方法定义在构造函数外部,构造函数实例化执行的时候,只是一个指针指向一个函数,而不会再次创建,但这又带来新的问题:

  1. 如果需要定义很多方法,那就要声明很多变量了,严重污染全局空间;
  2. 同时这些变量只是为了某个函数创建的,虽然也可以直接在外部使用,但没有意义,这有点浪费;
        <script type="text/javascript">
var Person = function (user) {
this.user = user;
this.who = who; //注意这里是传函数,不是传返回值,不要带()
}; function who(){
return this.user;
} var shane = new Person ('shane');
var lucy = new Person ('lucy');
console.log(shane.who);//function who(){return this.user;}
console.log(shane.who());//shane 带上()执行,不带()就只是函数了 console.log(shane.who === lucy.who);//true
//方法定义在构造函数外部,构造函数实例化执行的时候,只是一个指针指向一个函数,而不会再次创建
//但这又带来另一问题,如果需要定义很多方法,那就要声明很多变量了,严重污染全局空间
//同时这些变量只是为了某个函数创建的,虽然也可以直接在外部使用,但没有意义,这有点浪费
</script>

  如果所有的对象实例共享同一个方法,并且不用声明变量,会更有效率,这就需要用到下面所说的原型对象;

原型对象

  构造函数、原型对象、实例对象之间的关系:

  1. 用来 new 初始化新创建对象的函数是构造函数;
  2. 通过构造函数的 new 操作创建的对象是实例对象。可以用一个构造函数,构造多个实例对象;
  3. 构造函数有一个 prototype 属性,指向其对应的原型对象;通过同一个构造函数实例化出来的多个对象具有相同的原型对象。经常使用原型对象来实现继承。
  4. 原型对象有一个 constructor 属性,指向其对应的构造函数;
  5. 实例对象有一个 _proto_ 属性,指向其对应的原型对象;
  6. 由于实例对象可以继承原型对象的属性,所以实例对象也拥有constructor属性,同样指向原型对象对应的构造函数;
        <script type="text/javascript">
var Person = function (user) { //Person 就是构造函数
this.user = user;
};
Person.prototype.who = function(){ // Person.prototype就是原型对象
return this.user;
} var shane = new Person ('shane'); //shane就是实例对象
console.log(shane.user);//shane
console.log(shane.who());//shane console.log(Person.prototype.constructor === Person);//true
//原型对象有一个 constructor 属性,指向其对应的构造函数; console.log(shane.constructor === Person);//true
//每个实例对象在创建时,都自动拥有一个属性constructor,指向其构造函数;
//这个constuctor属性实际上继承自原型对象; console.log(shane.__proto__ === Person.prototype);//true
//实例对象有一个 __proto__ 属性,指向其对应的原型对象; Person.prototype.constructor = 123; //constructor属性可以被修改
console.log(shane.constructor === Person);//false 构造函数的属性可以被修改,所以不推荐用来检测实例和构造函数之间的关系
console.log(shane instanceof Person);//true 建议使用instanceof来检测实例和构造函数之间的关系。
</script>

  一般地,可以通过 isPrototypeOf() 方法来判断实例对象和原型对象的关系;

  ES5 新增了 Object.getPrototypeOf() 方法,该方法返回实例对象对应的原型对象;

        <script type="text/javascript">
var Person = function () {}; //Person 就是构造函数
var shane = new Person; //shane就是实例对象 console.log(shane.__proto__ === Person);//false
console.log(shane.__proto__ === Person.constructor);//false
console.log(shane.__proto__ === Person.prototype);//true Person.prototype就是原型对象
//实例对象有一个 _proto_ 属性,指向该实例对象对应的原型对象; console.log(Person.isPrototypeOf(shane));//false
console.log(Person.constructor.isPrototypeOf(shane));//false
console.log(Person.prototype.isPrototypeOf(shane));//true
//我们可以用 Obj1.isPrototypeOf(obj2);来判断 Obj1 是否为 obj2 的原型对象 console.log(Object.getPrototypeOf(shane) === Person.prototype); //true
//ES5新增了Object.getPrototypeOf()方法,该方法返回实例对象对应的原型对象 console.log(Object.getPrototypeOf(shane) === shane.__proto__); //true
//实际上,Object.getPrototypeOf()方法和__proto__属性是一回事,都指向原型对象
</script>

属性查找

  当读取一个对象的属性时,引擎首先在该对象的自有属性中查找属性名字。如果找到则返回;如果自有属性不包含该名字,则会顺着__proto__搜索原型链中的对象。如果找到则返回。如果找不到,则返回 undefined;

  in操作符可以判断属性在不在该对象上,但无法区别自有属性还是原型属性;

  通过 obj.hasOwnProperty(attr) 方法可以确定该属性是否为自有属性;

  于是可以将 obj.hasOwnProperty(attr) 方法和 in 运算符结合起来使用,用来鉴别原型属性;

        <script type="text/javascript">
var Person = function(){
this.name = 'shane';
};
Person.prototype.height = 173; var man = new Person;
man.age = 25;
console.log('name' in Person);//true 属性要用引号包起来
console.log('age' in Person);//false
console.log('height' in Person);//false
console.log('height' in Person.prototype);//true
console.log(Person.hasOwnProperty('name'));//true
console.log(Person.hasOwnProperty('age'));//false
console.log(Person.hasOwnProperty('height'));//false console.log('name' in man);//true
console.log('age' in man);//true
console.log('height' in man);//true
console.log(man.hasOwnProperty('name'));//true
console.log(man.hasOwnProperty('age'));//true
console.log(man.hasOwnProperty('height'));//false // obj.hasOwnProperty(attr) 和 in 操作符结合起来可以用来鉴别原型属性
// (attr in obj && !obj.hasOwnProperty(attr)) 为真则为原型属性;
console.log('name' in man && !man.hasOwnProperty('name')); //false
console.log('age' in man && !man.hasOwnProperty('age')); //false
console.log('height' in man && !man.hasOwnProperty('height')); //true
</script>

  原型对象可以存放公用属性或方法,但存放引用值时需要注意,实例对象可以修改原型上的这些公用的引用值,所以我们一般把引用值属性写在构造函数里,公用方法写在原型对象上;

        <script type="text/javascript">
var Person = function(name){
this.name = name;
};
Person.prototype.likes = ['apple','banana']; var shane = new Person('shane');
var lucy = new Person('lucy'); console.log(shane.likes); //['apple','banana']
shane.likes.splice(0,0,'car'); console.log(lucy.likes); //['car','apple','banana']
</script>

  我们可以用对象字面量的方式写原型对象上的属性和方法,使代码更加整洁;

  原型对象有一个 constructor 属性是指向其对应的构造函数的,所以当我们把一个对象字面量赋给原型对象时,会产生问题;

  解决方法就是在对象字面量里手动添加一个 constructor 属性,指向其对应的构造函数;

        <script type="text/javascript">
var Person = function(name){
this.name = name;
};
Person.prototype = {
constructor: Person,
//原型对象有一个 constructor 属性是指向其对应的构造函数的,所以当我们把一个对象字面量赋给原型对象时,会产生问题;
//在对象字面量里手动添加一个 constructor 属性,指向其对应的构造函数;
sayName: function(){
return this.name;
}
}; var shane = new Person('shane');
console.log(shane.sayName());//shane
console.log(shane.constructor === Person);//true
</script>

组合模式

  构造函数模式和原型模式结合起来使用是创建自定义类型的最常见方式。构造函数模式用于定义实例属性,而原型模式用于定义共享的方法,这种组合模式还支持向构造函数传递参数。实例对象都有自己的一份实例属性的副本,同时又共享对方法的引用,最大限度地节省了内存。

        <script type="text/javascript">
var Person = function(name){
this.name = name;
this.likes = ['apple','banana'];
};
Person.prototype = {
constructor: Person,
sayName: function(){
return this.name;
}
};
var shane = new Person('shane');
var lucy = new Person('lucy'); console.log(shane.likes); //['apple','banana'] shane.likes.splice(0,0,'car');
console.log(shane.likes); //['car','apple','banana']
console.log(lucy.likes); //['apple','banana']
</script>

扩展原型

  在不能直接访问原型的时候,如果能访问实例,就可以用 instance.constructor.prototype 去修改或扩展原型对象;

        <script type="text/javascript">
var shane;
(function(){
function Person (a,b) {
this.a = a;
this.b = b;
} Person.prototype.sayName = function () {
return this.a;
} shane = new Person('shane',25);
})();
console.log(shane.sayName());//shane //在不能直接访问原型的时候,如果能访问实例,就可以用 instance.constructor.prototype 去修改或扩展原型对象;
shane.constructor.prototype.sayAge = function(){
return this.b;
};
console.log(shane.sayAge());//25
</script>

jacascript 构造函数、原型对象和原型链的更多相关文章

  1. 还在问什么是JavaScript构造函数、实例、原型对象以及原型链?看完这篇你就懂

    1概述 ES6, 全称 ECMAScript 6.0 ,2015.06 发版.在ES6之前,对象不是基于类创建的,而是用一种称为构造函数的特殊函数来定义对象和它们的特征. 2构造函数 构造函数是一种特 ...

  2. 【javascript】对原型对象、原型链的理解

    原型对象,原型链这些知识属于基础类知识.但是平时开发过程中也很少用到. 看网上的意思,原型链用于es5开发场景下的继承.es6有了类语法糖之后,就自带继承了. 通过理解,个人画了一张原型链解构的关系图 ...

  3. javascript原型对象与原型链

    在javascript中,当系统加载构造函授后 ,会自动在内存中增加一个对象,这个对象就是原型对象.构造函数和原型对象在内存中表现为相互独立,但两者之间还存在联系,构造函数的prototype是原型对 ...

  4. JavaScript基础之原型对象和原型链

    原型对象 原型对象简单来说就是函数的原型所指向的对象.前面说原型的时候,说了Object.prototype所指对象就是Object(函数)的原型对象.在每个函数的原型对象中,默认会有construc ...

  5. 三张图较为好理解JavaScript的原型对象与原型链

    最近从网上看到别人详细得讲解了js的原型对象和原型链,看完感觉是看得最清晰的一个,于是,摘录到自己博客里 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与_ ...

  6. 对ES6中类class以及实例对象、原型对象、原型链之间关系的详细总结

    1. 类 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后用这个类来实例化对象.即类的用途:实例化对象. // 创建一个Person类 class Person { } // ...

  7. 三张图搞懂JavaScript的原型对象与原型链 / js继承,各种继承的优缺点(原型链继承,组合继承,寄生组合继承)

    摘自:https://www.cnblogs.com/shuiyi/p/5305435.html 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__pro ...

  8. 理解js中的原型,原型对象,原型链

    目录 理解原型 理解原型对象 实例属性与原型属性的关系 更简单的原型语法 原型的动态性 原型链 理解原型 我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象, ...

  9. JS核心系列:浅谈原型对象和原型链

    在Javascript中,万物皆对象,但对象也有区别,大致可以分为两类,即:普通对象(Object)和函数对象(Function). 一般而言,通过new Function产生的对象是函数对象,其他对 ...

随机推荐

  1. Redis学习笔记01--主从数据库配置

    1.创建公共配置文件 所有配置文件添加到以下目录: /xxxx/redis-slave-master 创建公共的redis配置文件,直接使用redis的默认配置文件,修改以下配置项: bind 127 ...

  2. Java语法基础(1)

    Java语法基础(1) 1.      Java是一门跨平台(也就是跨操作系统)语言,其跨平台的本质是借助java虚拟机 (也就是JVM(java virtual mechinal))进行跨平台使用. ...

  3. MySQL基本语句与经典习题

    [SQL语句大全] 本文用到的数据(5张表): customers: orders: orderitems: Products:  Vendors: 一.检索数据-select语句select pro ...

  4. 【眼见为实】自己动手实践理解READ COMMITTED && MVCC

    [眼见为实]自己动手实践理解 READ COMMITTED && MVCC 首先设置数据库隔离级别为读已提交(READ COMMITTED): set global transacti ...

  5. 【网络】 应用&传输层笔记

    应用层 应用层常用的协议和各自对应的TCP/UDP端口: DNS TCP/UDP 53 HTTP TCP 80 SMTP TCP 25 POP UDP 110 Telnet TCP 23 DHCP U ...

  6. 设计模式 --> (17)状态模式

    状态模式 允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类.它有两种使用情况: (1)一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为. (2)一个操作中 ...

  7. Springmvc 视频学习地址

    http://www.icoolxue.com/album/show/216/

  8. JS获得一个对象的所有属性和方法

    function displayProp(obj){ var names=""; for(var name in obj){ names+=name+": "+ ...

  9. MQTT TLS 加密传输

    MQTT TLS 加密传输 Mosquitto原生支持了TLS加密,TLS(传输层安全)是SSL(安全套接层)的新名称,生成证书后再配置一下MQTT代理,本文主要介绍Mqtt如何实现双向认证和单向认证 ...

  10. Struts2学习笔记二 配置详解

    Struts2执行流程 1.简单执行流程,如下所示: 在浏览器输入请求地址,首先会被过滤器处理,然后查找主配置文件,然后根据地址栏中输入的/hello去每个package中查找为/hello的name ...