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章

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FSociety

SQL中GROUP BY用法示例

GROUP BY我们可以先从字面上来理解,GROUP表示分组,BY后面写字段名,就表示根据哪个字段进行分组,如果有用Excel比较多的话,GROUP BY比较类...

5.1K20
来自专栏儿童编程

什么样的人生才是有意义的人生——没有标准的标准答案

【导读】其实我们可以跳出这个小圈圈去更加科客观地看一下这个世界。在夜晚的时候我们仰望天空,浩瀚的宇宙中整个地球只是一粒浮尘,何况地球上一个小小的人类?在漫长的历...

1.7K50
来自专栏儿童编程

天干地支五行八卦的对应关系

18790
来自专栏儿童编程

儿童创造力教育与编程教育的碰撞——MIT雷斯尼克教授最新理论梗概

儿童编程教育已经在我国各一线二线城市疯狂出现,颇有“烂大街”的趋势。我们不禁要问很多很多问题:

21770
来自专栏haifeiWu与他朋友们的专栏

复杂业务下向Mysql导入30万条数据代码优化的踩坑记录

从毕业到现在第一次接触到超过30万条数据导入MySQL的场景(有点low),就是在顺丰公司接入我司EMM产品时需要将AD中的员工数据导入MySQL中,因此楼主负...

26340
来自专栏儿童编程

我不是算命先生,却对占卜有了疑惑——如何论证“占卜前提”的正确与否

事出有因,我对《周易》感兴趣了很多年。只是觉得特别有趣,断断续续学习了一些皮毛。这几天又偶然接触到了《梅花易数》,觉得很是精彩,将五行八卦天干地支都串联了起来。...

13810
来自专栏儿童编程

《动物魔法学校》儿童学编程Scratch之“外观”部分

导读:本文通过一个案例《动物魔法学校》来学习Scratch语言的“外观”部分。之后通过一系列其他功能的综合运用对作品功能进行了扩展。

18640
来自专栏Ken的杂谈

【系统设置】CentOS 修改机器名

17230
来自专栏儿童编程

一张图理清《梅花易数》梗概

学《易经》的目的不一定是为了卜卦,但是了解卜卦绝对能够让你更好地了解易学。今天用一张思维导图对《梅花易数》的主要内容进行概括,希望能够给学友们提供帮助。

30840
来自专栏儿童编程

声音功能让儿童编程更有创造性

导读:Scratch中声音功能非常强大,除了常规的音效,你甚至可以模拟各种乐器的各个发音、设置节拍、休止……如果你愿意,甚至可以用它创作一个交响乐。我们可以引导...

13440

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励