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. Swiper.js

    Swiper常用于移动端网站的内容触摸滑动 http://idangero.us/swiper/#.WUCSo_mGOUk

  2. Hibernate——hibernate的配置测试

    Hibernate Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自 ...

  3. [js高手之路]设计模式系列课程-发布者,订阅者重构购物车

    发布者订阅者模式,是一种很常见的模式,比如: 一.买卖房子 生活中的买房,卖房,中介就构成了一个发布订阅者模式,买房的人,一般需要的是房源,价格,使用面积等信息,他充当了订阅者的角色 中介拿到卖主的房 ...

  4. 7.20.01 java格式化输出 printf 例子

    java格式化输出 printf 例子 importjava.util.Date; publicclassPrintf { publicstaticvoidmain(String[] args) { ...

  5. Andrew Ng机器学习课程笔记--week8(K-means&PCA)

    Unsupervised Learning 本周我们讲学习非监督学习算法,会学习到如下概念 聚类(clustering) PCA(Principal Componets Analysis主成分分析), ...

  6. Python爬取糗事百科

    import urllib import urllib.request from bs4 import BeautifulSoup """     1.抓取糗事百科所有纯 ...

  7. MyBatis记录

    记录一下MyBatis的几个模块大纲,除去缓存以及集合映射两个部分 Mybatis架构 1. mybatis配置 SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了myb ...

  8. Spring中AOP简介与切面编程的使用

    Spring中AOP简介与使用 什么是AOP? Aspect Oriented Programming(AOP),多译作 "面向切面编程",也就是说,对一段程序,从侧面插入,进行操 ...

  9. 数据结构之R进制转换

    废话不多说,直接上代码 #include <stdio.h> int exchange(int a) { char c='A'; if(a>=10) { printf("% ...

  10. postman 第1节 安装启动(转)

    安装: 1.mac app安装 浏览器访问https://www.getpostman.com/apps,选择Get the Mac App,下载安装即可 2.chrome app安装 浏览器访问ht ...