JS入门难点解析11-构造函数,原型对象,实例对象

(注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!)
(注2:更多内容请查看我的目录。)

1. 简介

在前面,我们对这三个概念已经有所涉及,但是却并未深究。事实上,如果能熟练理解掌握这三个概念和他们之间的关系,那么在学习原型链和继承的知识时,会有一种拨云见雾之感。

2. 构造函数

构造函数其实与普通函数本身并无区别,普通函数通过new调用时,我们就称其为构造函数。当然,为了区分其与普通函数,构造函数约定首字母需要大写。下面,我们就来看一下构造函数和普通函数使用时的区别(简单来讲就是一个函数通过new调用和不通过new调用的区别)。

2.1 一个空函数

// 空函数
function A() {}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);  //undefined
console.log('a2:', a2);  //{}

在chrome的控制台console运行结果如图所示:

2.1

直接调用返回undefined,而使用new调用返回的却是一个空对象。这里,我们暂且不去讨论_proto_和constructor的含义。

2.2 无this有return,但是return后面无返回值,或者返回基本类型值。

// 无返回值
function A() {
    return;
}
//返回undefined类型值
function B() {
    return undefined;
}
// 返回Number类型值
function C() {
    return 1;
}
// 返回String类型值
function D() {
    return '1';
}
// 返回Boolean类型值
function E() {
    return true;
}
// 返回Null类型值
function F() {
    return null;
}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);
console.log('a2:', a2);
var b1 = B();
var b2 = new A();
console.log('b1:', b1);
console.log('b2:', b2);
var c1 = C();
var c2 = new A();
console.log('c1:', c1);
console.log('c2:', c2);
var d1 = D();
var d2 = new A();
console.log('d1:', d1);
console.log('d2:', d2);
var e1 = E();
var e2 = new A();
console.log('e1:', e1);
console.log('e2:', e2);
var f1 = F();
var f2 = new A();
console.log('f1:', f1);
console.log('f2:', f2);
2.2

可以看到,普通调用会返回return后面的值,而new调用返回空对象{}。

2.3 无this有return,但是return后面是一个对象(包括函数)。

// 返回对象
function A() {
    return {m: 1};
}
//返回函数
function B() {
    return function () {
        return 123;
    }
}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);
console.log('a2:', a2);
var b1 = B();
var b2 = new B();
console.log('b1:', b1);
console.log('b2:', b2);
2.3

可以看出,不管是普通调用还是new调用都是返回return后面的值。

2.4 有this,无return。

function A() {
    this.m = 1;
    this.n = function () {
        console.log(123);
    };
}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);
console.log('a2:', a2);
2.4

普通调用返回undefined,而new调用返回一个对象,构造函数A中的this指向了该对象,所以返回对象的属性和方法由构造函数中的this语句初始化。
ps: 需要注意的是,普通调用的时候,this指向了undefined,非严格模式下指向了widow。

2.5 有this,有return。但是return后面无返回值,或者返回基本类型值。

// 无返回值
function A() {
    this.m = 1;
    return;
}
//返回undefined类型值
function B() {
    this.m = 1;
    return undefined;
}
// 返回Number类型值
function C() {
    this.m = 1;
    return 1;
}
// 返回String类型值
function D() {
    this.m = 1;
    return '1';
}
// 返回Boolean类型值
function E() {
    this.m = 1;
    return true;
}
// 返回Null类型值
function F() {
    this.m = 1;
    return null;
}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);
console.log('a2:', a2);
var b1 = B();
var b2 = new B();
console.log('b1:', b1);
console.log('b2:', b2);
var c1 = C();
var c2 = new C();
console.log('c1:', c1);
console.log('c2:', c2);
var d1 = D();
var d2 = new D();
console.log('d1:', d1);
console.log('d2:', d2);
var e1 = E();
var e2 = new E();
console.log('e1:', e1);
console.log('e2:', e2);
var f1 = F();
var f2 = new F();
console.log('f1:', f1);
console.log('f2:', f2);
2.5

可以看到,普通调用会返回return后面的值,而new调用返回一个对象,构造函数A中的this指向了该对象,所以返回对象的属性和方法由构造函数中的this语句初始化。
ps: 需要注意的是,普通调用的时候,this指向了undefined,非严格模式下指向了widow。

2.6 有this,有return。return后面是一个对象(包括函数)。

// 返回对象
function A() {
    this.m = 1;
    return {n: 2};
}
//返回函数
function B() {
    this.f = function () {
        return 1;
    };
    return function () {
        return 2;
    }
}
var a1 = A();
var a2 = new A();
console.log('a1:', a1);
console.log('a2:', a2);
var b1 = B();
var b2 = new B();
console.log('b1:', b1);
console.log('b2:', b2);
2.6

可以看到,不管是普通调用还是new调用都是返回return后面的值。
ps: 需要注意的是,普通调用的时候,this指向了undefined,非严格模式下指向了widow。

总结:对于构造函数调用,有如下特点:

  1. 如果没有return,返回一个新的对象,构造函数的this指向该对象。
  2. 如果有return且后面的返回值不是对象(包括函数),则return语句会被忽略。
  3. 如果有return且后面返回一个对象(包括函数),则返回该对象。

3. 实例对象

第2节我们已经阐述了构造函数的定义和使用方法,现在我们来看一下实例对象的定义。

实例对象:通过构造函数的new操作创建的对象是实例对象,又常常被称为对象实例。可以用一个构造函数,构造多个实例对象。下面的f1和f2就是实例对象。

function Foo(){};
var f1 = new Foo;
var f2 = new Foo;
console.log(f1 === f2);//false

4. 原型对象

首先,我们来看两段《JavaScrpit高级程序设计》对原型模式和原型对象的阐述:

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。

简而言之,任何一个函数,都拥有一个prototype属性,指向其原型对象,该原型对象也是由该函数new调用创造的所有实例对象的原型对象。

5. 构造函数,原型对象和实例对象的关系

5.1 指向关系

构造函数A的prototype属性指向F与其实例对象(a1,a2,...)的原型对象A.prototype,该原型对象的constructor属性指向构造函数A,实例对象拥有[[Prototype]]属性(在firefox,safari和chrome上该属性实现为_proto_)指向原型对象A.prototype

function A() {
}
var a1 = new A();
var a2 = new A();
5.1

还记得我们在前面2.1节的空函数为构造函数的图片吗?现在来看是不是就很清晰了。明白了其中的指向关系,我们再来看一下,构造函数中添加this语句以及在原型对象中添加属性以后是怎样的情况。

5.2 实例化时的数据关系

// 代码段5.2
function A() {
    this.m = 1;
    this.n = [1, 2];
}

A.prototype.p = 2;
A.prototype.q = [3, 4];

var a1 = new A();
var a2 = new A();

当使用构造函数新建实例对象时,各个实例对象都会拥有由this指定的属性。


5.2

5.3 实例对象属性赋值和使用时的关系(可以类比LHS和RHS)

5.3.1 使用时的继承关系

使用实例对象属性时,如果该属性不存在于实例对象,就会使用其原型对象该属性。
在代码段5.2执行之后做如下操作:

// 代码段5.3.1,承接代码段5.2
console.log('a1.m:', a1.m);
console.log('a2.m:', a2.m);

console.log('a1.n:', a1.n);
console.log('a2.n:', a2.n);

console.log('a1.p:', a1.p);
console.log('a2.p:', a2.p);

console.log('a1.q:', a1.q);
console.log('a2.q:', a2.q);
5.3.1

如图所示,打印a1.m会找到其实例对象属性m,而a1.p会找到其原型对象属性p。

5.3.2 使用查找时的先后关系(赋值时的覆盖关系)

使用实例对象属性时,优先从实例对象查找该属性,如果该属性不存在,就会使用其原型对象该属性。而对实例对象属性的赋值操作,将会直接使用实例对象属性。

// 代码段5.3.2.1,承接代码段5.3.1
a1.p = 11;

console.log('a1.p:', a1.p);
console.log('a2.p:', a2.p);
5.3.2.1

说明,a1.p是给a1添加了属性p并赋值11,但是此时a2是没有该属性的,所以对a2.p的使用会查找到A.prototype。

要注意的是,这里实例对象属性之间是互相独立的,而原型对象属性是共享的。

// 代码段5.3.2.2,承接代码段5.3.2.1
a1.n.push(3);
a1.q.push(5);

console.log('a1.n:', a1.n);
console.log('a2.n:', a2.n);

console.log('a1.q:', a1.q);
console.log('a2.q:', a2.q);
5.3.2.2

可以看到,对原型对象属性为对象时的操作( 堆操作)会影响到其他的实例对象对该属性的使用。

另外,还有一点要注意,如果你对对象使用的是赋值操作,并不会影响到原型属性。不明白的同学再看一下5.3.2.1。

6. 总结

其实,我们用代码解释一下new函数构造一个实例的过程。
对于

function A(m, n) {
    this.m = m;
    this.n = n;
}

var a = new A(1, 2);
console.log(a);
6.1

中的 new A(1,2)这一步操作,其实可以分解为如下四个步骤:

// 新建一个空对象obj
let obj  ={};
// obj的__proto__属性指向原型对象
obj.__proto__ = A.prototype;
// 将构造函数的this绑定obj,传入构造函数的参数,并将返回结果赋值给result
let result = A.apply(obj, arguments);
// 如果result存在且result是对象或者函数,则构造函数返回result,否则将返回obj
return (result && (typeof(result) === 'object' || typeof(result) === 'function')?result:obj);
  1. 新建一个空对象obj
  2. obj的proto属性指向原型对象
  3. 将构造函数的this绑定obj,传入构造函数的参数,并将返回结果赋值给result
  4. 如果result存在且result是对象或者函数,则构造函数返回result,否则将返回obj

我们可以试着模拟一个函数myNewA,如下:

function A(m, n) {
    this.m = m;
    this.n = n;
}

function myNewA() {
    let obj  ={};
    obj.__proto__ = A.prototype;
    let result = A.apply(obj, arguments);
    return (result && (typeof(result) === 'object' || typeof(result) === 'fucntion')?result:obj);
}
var a = myNewA(1, 2)
console.log(a);
6.2

可以看到,结果和6.1一模一样,当然了,真正的new构造函数的过程不会是这么简单,我们只是通过这个例子使大家能够加深对构造函数,原型对象和实例对象的理解。

参考

javascript面向对象系列第一篇——构造函数和原型对象
JS入门难点解析10-创建对象
深入理解js构造函数
JavaScript构造函数详解
BOOK-《JavaScript高级程序设计(第3版)》第6章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,547评论 4 374
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,787评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,175评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,752评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,169评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,056评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,195评论 2 321
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,980评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,724评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,872评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,344评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,667评论 3 264
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,379评论 3 245
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,202评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,992评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,189评论 2 286
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,987评论 2 279

推荐阅读更多精彩内容

  • 本文把程序员所需掌握的关键知识总结为三大类19个关键概念,然后给出了掌握每个关键概念所需的入门书籍,必读书籍,以及...
    dle_oxio阅读 10,991评论 6 244
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,112评论 18 139
  • (注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!)(注2:更多内容请查看我的目录。) ...
    love丁酥酥阅读 807评论 3 2
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,598评论 25 707
  • 总会有些不懂我们的人,对我们产生偏见。 装纯。有同事,也有同学这样说过我。不明白他(她)为什么会这样说?说实话,...
    佛林阅读 498评论 0 0