1、JS中对象的”不同”:原型概念

从Java中我们可以很好地去理解 “类” 和 “实例” 两个概念,可是在JavaScript中,这个概念却不一样。

JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。

原型是指当我们想要创建一个具体对象时,并没有像Java中那样有类可以使用,但是却可以利用类似“继承”的方式,这里类似“父类”的对象,就是所谓的原型。(再简单点:原型也是一个对象,通过原型可以实现对象的属性继承

比如:我们有一个Student对象,现在我们通过它作为原型,创建出xiaoming:
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
}; var xiaoming = {
name: '小明'
}; xiaoming.__proto__ = Student; //xiaoming的原型指向了对象Student
13
 
1
var Student = { 
2
    name: 'Robot',
3
    height: 1.2,
4
    run: function () {
5
        console.log(this.name + ' is running...');
6
    }
7
};
8

9
var xiaoming = {
10
    name: '小明'
11
};
12

13
xiaoming.__proto__ = Student; //xiaoming的原型指向了对象Student

这里的xiaoming有自己的name,但是没有定义run()方法,不过没关系,他是“继承“Student,所以只要Student有run()方法,xiaoming也就可以直接使用。

Object.create() 方法可以传入一个原型对象,并创建一个基于该原型的新对象,但这个新对象什么属性也没有。基于以上,我们也可以编写一个函数来创建xiaoming:
// 原型对象:
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
}; function createStudent(name) {
// 基于Student原型创建一个新对象:
var s = Object.create(Student);
// 初始化新对象:
s.name = name;
return s;
} var xiaoming = createStudent('小明');
xiaoming.run(); // 小明 is running...
xiaoming.__proto__ === Student; // true
20
 
1
// 原型对象:
2
var Student = {
3
    name: 'Robot',
4
    height: 1.2,
5
    run: function () {
6
        console.log(this.name + ' is running...');
7
    }
8
};
9

10
function createStudent(name) {
11
    // 基于Student原型创建一个新对象:
12
    var s = Object.create(Student);
13
    // 初始化新对象:
14
    s.name = name;
15
    return s;
16
}
17

18
var xiaoming = createStudent('小明');
19
xiaoming.run(); // 小明 is running...
20
xiaoming.__proto__ === Student; // true

2、创建对象 和 原型的理解

当我们使用 obj.xxx 访问一个对象的属性时,这个过程和Java中访问属性极为相似,都是顺着继承链不断往上搜寻。JavaScript引擎会先在当前对象查找,没找到就到其原型对象上找,一直如此直到追溯到 Object.prototype 对象,最后如果还没有找到,就只能返回 undefined。

比如我们有Array对象 var arr = [1, 2, 3]; 其原型链是 arr --> Array.prototype --> Object.prototype --> null,indexOf()等方法都是在Array.prototype中定义的,所以你可以在所有的Array对象上直接调用这些方法。

创建对象的方式:
  • 使用 { ... } 创建
  • 使用 new 构造函数

function Student(name) {
this.name = name;
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
//这确实是一个普通函数,如果不写new的话;
//如果写了new,它就是一个构造函数,默认返回this,不需要在最后写return this; var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明! //这里的xiaoming原型就指向这个构造函数Student了
14
 
1
function Student(name) {
2
    this.name = name;
3
    this.hello = function () {
4
        alert('Hello, ' + this.name + '!');
5
    }
6
}
7
//这确实是一个普通函数,如果不写new的话;
8
//如果写了new,它就是一个构造函数,默认返回this,不需要在最后写return this;
9

10
var xiaoming = new Student('小明');
11
xiaoming.name; // '小明'
12
xiaoming.hello(); // Hello, 小明!
13

14
//这里的xiaoming原型就指向这个构造函数Student了

new 的方式创建的对象,还从原型上获得了一个 constructor 属性,这个属性指向构造函数本身:
xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true Object.getPrototypeOf(xiaoming) === Student.prototype; // true xiaoming instanceof Student; // true
6
 
1
xiaoming.constructor === Student.prototype.constructor; // true
2
Student.prototype.constructor === Student; // true
3

4
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
5

6
xiaoming instanceof Student; // true

好了好了,到这里就有点乱,什么constructor、prototype、__proto__ 等等,我们来理理概念先:
  • constructor 属性,指向对象的构造函数,每个对象都有
  • prototype 属性,是可以作为构造函数的函数对象,才具备的属性,比如这里的Student
  • __proto__ 属性是任何对象(除了null)都具备的属性
  • 上面两者的指向,都是其所属的原型对象。所以为什么构造函数有了__proto__还要有一个同样的prototype呢,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(即设置实例的__proto__属性)

所以上面我们提到的Student,我们输出它的prototype看看:

再看看小明的__proto__,很明显和Student.prototype是一样的,毕竟是赋值过来的:

可以看到,它们的原型对象有两个属性:constructor 和 __proto__ :
  • 前者指向构造函数Student(),相当于是标记自己是和Student这个构造函数发生联系的;
  • 后者指向是这个原型对象的原型对象,这里是Object

廖老师的原文中用了一张图来解释这些关系:
你可以把构造函数想象成女人A,某个实例对象是男人B(这个实例对象由其他构造函数产生),女人通过prototype属性指向男人,男人通过constructor属性指向女人,相互指向以后就相当于结婚仪式了,这下好了是夫妻了。

女人生下孩子时,就通过把prototype赋值给孩子的__proto__属性,相当于告诉他们让他们知道自己的父亲是谁。既然这些孩子知道父亲是谁了,正所谓 “龙生龙,凤生凤,老鼠的儿子会打洞”,这些孩子虽然本来没有学过什么技能,但是也可以通过他们的老爹继承,自然而然地会使用一些技能了。

所以,记住这句话,实际上,原型继承的本质是 “将构造函数的原型对象,指向由另一个构造函数创建的实例”

以上,我们可以再看看Student的 prototype 和 __proto__:

我们可以看到:
  • prototype指向的原型对象,也就是xiaoming的老爹,是个没什么多余属性的几乎空白的一个对象,所以xiaoming没得到什么新能力
  • 构造函数的原型对象(就是它爹)是一个空的函数 function( ) { }

看到这里,再结合之前的一个示例代码,我们稍微改动一下,当小明发现他的爹实际上是隔壁老王:
var laoWang = {
name: '隔壁老王',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
}; var xiaoming = {
name: '小明'
}; xiaoming.__proto__ = laoWang; //xiaoming的原型指向了对象Student //相当于告诉小明的爹是谁,比如小明的爹变成了老王 xiaoming.run(); // 小明 is running... //小明没定义run方法也会使用run()了
15
 
1
var laoWang = {
2
    name: '隔壁老王',
3
    height: 1.2,
4
    run: function () {
5
        console.log(this.name + ' is running...');
6
    }
7
};
8

9
var xiaoming = {
10
    name: '小明'
11
};
12

13
xiaoming.__proto__ = laoWang; //xiaoming的原型指向了对象Student //相当于告诉小明的爹是谁,比如小明的爹变成了老王
14

15
xiaoming.run(); // 小明 is running...  //小明没定义run方法也会使用run()了

需要注意的是,方法定义在构造函数中比如run定义在构造函数中,多个对象虽然都有run但是run方法是不同的,用 “===” 比较会得到 false;实际上他们可以共享同一个函数,把这个函数移动到原型对象里就好了,那么多个对象的方法相当于是继承的,而不是构造函数定义的,用 "===" 比较就会得到true。
function Student(name){
this.name = name;
this.run = function(){return this.name + "is running..."}
} var xiaoming = new Student("xiaoming");
var xiaohong = new Student("xiaohong"); xiaoming.run === xiaohong.run;
//false ----------------------------------------------------------------------
function Student(name){
this.name = name;
} Student.prototype.run = function(){return this.name + "is running..."} xiaoming = new Student("xiaoming");
xiaohong = new Student("xiaohong"); xiaoming.run === xiaohong.run;
//true
23
 
1
function Student(name){
2
  this.name = name;
3
  this.run = function(){return this.name + "is running..."}
4
}
5

6
var xiaoming = new Student("xiaoming");
7
var xiaohong = new Student("xiaohong");
8

9
xiaoming.run === xiaohong.run;
10
//false
11

12
----------------------------------------------------------------------
13
function Student(name){
14
  this.name = name;
15
}
16

17
Student.prototype.run = function(){return this.name + "is running..."}
18

19
xiaoming = new Student("xiaoming");
20
xiaohong = new Student("xiaohong");
21

22
xiaoming.run === xiaohong.run;
23
//true

3、其他的话

按照约定,构造函数的首字母要大写,普通函数的首字母才小写,用以区分。

调用构造函数的时候千万不要忘记写 new,否则函数作为普通函数被运行,this 会被绑定到全局对象window,无意间你就创建了一个全局变量。

我们可以写一个 createXXX() 函数,把 new 的操作封装进去:
function Student(props) {
this.name = props.name || '匿名'; // 默认值为'匿名'
this.grade = props.grade || 1; // 默认值为1
} Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}; function createStudent(props) {
return new Student(props || {})
}
12
 
1
function Student(props) {
2
    this.name = props.name || '匿名'; // 默认值为'匿名'
3
    this.grade = props.grade || 1; // 默认值为1
4
}
5

6
Student.prototype.hello = function () {
7
    alert('Hello, ' + this.name + '!');
8
};
9

10
function createStudent(props) {
11
    return new Student(props || {})
12
}

这种方式不会遗漏new,参数也很灵活,可以不传,也可以传一个对象:
var xiaoming = createStudent({
name: '小明'
}); xiaoming.grade; // 1
x
 
1
var xiaoming = createStudent({
2
    name: '小明'
3
});
4

5
xiaoming.grade; // 1

如果创建的对象有很多属性,我们只需要传递需要的某些属性,剩下的属性可以用默认值。由于参数是一个Object,我们无需记忆参数的顺序。如果恰好从JSON拿到了一个对象,就可以直接创建出xiaoming。


除了廖老师的教程参考,其他参考链接:

04面向对象编程-01-创建对象 和 原型理解(prototype、__proto__)的更多相关文章

  1. JavaSE学习笔记05面向对象编程01

    面向对象编程01 java的核心思想就是OOP 面向过程&面向对象 面向过程思想: 步骤清晰简单,第一步做什么,第二步做什么...... 面向过程适合处理一些较为简单的问题 面向对象思想: 物 ...

  2. JavaScript面向对象编程(一)原型与继承

    原型(prototype) JavaScript是通过原型(prototype)进行对象之间的继承.当一个对象A继承自另外一个对象B后,A就拥有了B中定义的属性,而B就成为了A的原型.JavaScri ...

  3. JavaScript中的面向对象编程,详解原型对象及prototype,constructor,proto,内含面向对象编程详细案例(烟花案例)

    面向对象编程:   面向:以什么为主,基于什么模式 对象:由键值对组成,可以用来描述事物,存储数据的一种数据格式 编程:使用代码解决需求   面向过程编程:         按照我们分析好的步骤,按步 ...

  4. 04面向对象编程-02-原型继承 和 ES6的class继承

    1.原型继承 在上一篇中,我们提到,JS中原型继承的本质,实际上就是 "将构造函数的原型对象,指向由另一个构造函数创建的实例". 这里,我们就原型继承的概念,再进行详细的理解.首先 ...

  5. 深入理解JavaScript原型:prototype,__proto__和constructor

    JavaScript语言的原型是前端开发者必须掌握的要点之一,但在使用原型时往往只关注了语法,其深层的原理并未理解透彻.本文结合笔者开发工作中遇到的问题详细讲解JavaScript原型的几个关键概念, ...

  6. 原型理解:prototype

    这一系列的链接的原型对象就是原型链(prototype chain) 1.所有对象都有同一个原型对象,都可通过Object.prototype获得对象引用 2.new出来的构造函数对象原型就是构造函数 ...

  7. 面向对象编程技术的总结和理解(c++)

    目录树 1.继承 1.1 基类成员在派生类中的访问属性 1.2继承时导致的二义性 1.3 多基继承 2.虚函数的多态 2.1虚函数的定义 2.2派生类中可以根据需要对虚函数进行重定义 2.3 虚函数的 ...

  8. python面向对象编程对象和实例的理解

    给你一个眼神,自己体会

  9. 创建对象_原型(Prototype)模式_深拷贝

      举例:     刚给我的客厅做了装修,朋友也希望给他的客厅做装修,他可能会把我家的装修方案拿过来改改就成,我的装修方案就是原型.   定义:     使用原型实例指定将要创建的对象类型,通过复制这 ...

随机推荐

  1. node.js之模块

    node.js之模块 1.自定义模块的设置 加载自定义模块利用require: eg: require('./custom_module.js') 2.从模块外部访问模块内的成员 2.1使用expor ...

  2. 常用perl脚本工具

    1.批量添加license:PrefixLicense.pl (1)给单一c/c++源文件添加license: perl PrefixLicense.pl apache_license src_fil ...

  3. JavaScript模块化 --- Commonjs、AMD、CMD、es6 modules

    随着前端js代码复杂度的提高,JavaScript模块化这个概念便被提出来,前端社区也不断地实现前端模块化,直到es6对其进行了规范,下面就介绍JavaScript模块化. 这篇文章还是希望能给大家一 ...

  4. CSS选择器大汇总

    CSS选择器是学习CSS以及Web编程的基础. 整理出常用的CSS选择器,供自己和大家一起学习. 基本选择器 * /*通用元素选择器,匹配页面任何元素(这也就决定了我们很少使用)*/ #id /*id ...

  5. centos7.1磁盘分区 格式化 挂载

    1.fdisk -l 查看磁盘状态 2.将 /dev/sdb 分区 fdisk /dev/sdb 3.对分区进行格式化 mkfs -t ext3 /dev/sdb 4.挂载/dev/sdb 到/hom ...

  6. Springboot+resteasy定时任务

    定时任务 需求:按每日统计点赞数.评论数.热度的增加量(不是现有量) 1.每天零点执行:首先遍历出user的统计字段 然后插入到新创建的表中. 2.每天一点执行:根据时间段将两表的数据相减创建增量字段 ...

  7. jquery表单验证源码

    /**数据验证完整性**/$.fn.Validform = function () {    var Validatemsg = "";    var Validateflag = ...

  8. org.apache.commons.lang.StringUtils 中 Join 函数

    转自 http://my.oschina.net/zenglingfan/blog/134872 写代码的时候,经常会碰到需要把一个List中的每个元素,按逗号分隔转成字符串的需求,以前是自己写一段比 ...

  9. [2017-09-04]Abp系列——为什么值对象必须设计成不可变的

    本系列目录:Abp介绍和经验分享-目录 这篇是之前翻备忘录发现漏了的,前阵子刚好同事又提及过这个问题,这里补上. 本文重点在于理解什么是值对象的不可变性. Abp的ValueObject以及EF的Co ...

  10. js、JSP、servlet之间的传递小结

    @ JS 与 JSP :JSP无法直接获取JS的值,只能通过隐藏表单或者dom节点设置. JSP中设置隐藏表单input,或者设置任意一个隐藏或者不隐藏的节点比如div, 而JS就通过document ...