前言:
本文章带有强烈的个人风格主义,我以自己的方式理解原型链,还请各位路过的大佬们,多多指点,有啥不懂直接提出来,大家多多交流。
构造函数:
什么是构造函数?
var afunc = function (name) { // afunc就是构造函数!!!
this.name = name
this.get = function() {
return this.name;
};
}
var robotA = new afunc('html');
console.log( robotA.get() ); // "html"
var robotB = new afunc('css');
console.log( robotB.get() ); // "css"
var robotC = new afunc('javascript');
console.log( robotC.get() ); // "javascript"
构造函数像一个克隆机器,它能够源源不断地克隆相同的东西,机器人A、机器人B..... ,都有相同的 属性和方法 ;
判断一个对象的构造函数:
就像上面例子一样,我们怎么找到,一个实例对象(robotA,robotB, robotC)的构造函数呢???
var afunc = function (name) { // afunc就是构造函数!!!
this.name = name
this.get = function() {
return this.name;
};
}
var robotA = new afunc('html');
console.log(robotA.constructor); // 函数 afunc
办法很简单,通过 实例对象(robotA
)的 constructor
属性,就可以找到此实例对象的构造函数啦!!!
那大家有没有考虑过一个问题,我们声明的 afunc构造函数 又是谁帮我们生成的呢???用上述办法试试:
var afunc = function (name) { // afunc就是构造函数!!!
this.name = name
this.get = function() {
return this.name;
};
}
var robotA = new afunc('html');
console.log(afunc.constructor); // function Function() { }
很显然,我们声明的构造函数 afun
是由另一个构造函数 Function
生成的!!!
读到这里,想必你肯定很疑惑,那么 这个 Function
函数又是谁构造的呢 ???
我们先把这些疑惑先放放,我后面会解释,在这之前,我们先看看,JS 内置的像 Function
一样的函数有哪些?
JS 内置的构造函数:
聪明的读者,想必都已经想到了,我们声明一个数字、字符串等等内置类型都 可能 有构造函数!!!
// 7种内置类型依次试试,不知道哪7种的同学可以看看我另一篇文章 《JS灵巧判断7种类型的方式》
/*-----------------显然两个货没有构造函数---------------------*/
console.log(null) // null
console.log(undefined) // undefined
/*-----------------七种类型中的其他 5 种类型--------------------*/
console.log(Number); // ƒ Number() { }
console.log(String); // ƒ String() { }
console.log(Object); // ƒ Object() { }
console.log(Boolean); // ƒ Boolean() { }
console.log(Symbol); // ƒ Symbol() { }
--------------------7种类型之外的内置构造函数---------------------*/
console.log(Date); // ƒ Date() { }
console.log(RegExp); // ƒ RegExp() { }
/*--------------------注意:Math不是构造函数而是一个对象------------*/
console.log(Math); // [object Math] { ... }
大概就列出来这么些 内置函数 吧,那我们要怎样使用这些构造函数呢???
当我们声明一个字符串、数字时,这些函数就会开始工作,证明这个真理:
var n = 123; // 当你写的 123 会被浏览器用 Number() 内置函数构造出来;
console.log(n.constructor); // ƒ Number() { }
var s = 'javascript';
console.log(s.constructor); // f String() { }
// 懒,其他我就省略啦 ......
// 其实你也可以用 new 关键字声明一个 数字、字符......,不过用这种方法的人该多蠢!!!
var n = new Number(123);
var s = new String('javascript');
console.log(typeof n); // "object"
console.log(typeof s); // "obejct"
console.log(Number(n)); // w3c 上是通过强制转换来获取 123 这个值的,
你可以很清晰地看到,其实,我们的这里个 内置函数 跟我们声明的 afunc
没有区别,都是同样的操作!!!
现在,我们再来看一个有趣的现象,我们这些内置函数的构造函数又是谁???
Number.constructor // ƒ Function() { } 构造者是 Function 函数
String.constructor // ƒ Function() { } 构造者是 Function 函数
Object.constructor // ƒ Function() { } 构造者是 Function 函数
Boolean.constructor // ƒ Function() { } 构造者是 Function 函数
Symbol.constructor // ƒ Function() { } 构造者是 Function 函数
Date.constructor // ƒ Function() { } 构造者是 Function 函数
RegExp.constructor // ƒ Function() { } 构造者是 Function 函数
Math.constructor // ƒ Object() { } 注意:Math已经不是一个函数了,构造者是一个Object函数!!!
现在,我们清楚了:
- 我们每声明一个函数,浏览器解析到这个函数是会交给内置函数
Function
来构造!!! - 再看看
Math
对象,你也已经可能猜到,对象是由f Object()
函数所构造的!!!
相类似,我们用 var关键字声明一个字符串、数字、布尔值等等,全部都是由浏览器解析然后交给内置函数处理!
函数对象和普通对象
估计看到标题你就傻了吧,函数对象是什么鬼?其实,函数的本质也是一个对象,只不过函数的能力很强大罢了!
// 如何区分,函数对象和普通对象???
var o = {};
var f = function(){ };
console.log( o.constructor ); // ƒ Object() { }
console.log( f.constructor ); // ƒ Function() { }
结论:普通对象的构造函数是 Object()
, 函数对象的构造函数是 Function()
;
原型的本质:
本篇文章是讲原型,前面是基础知识,现在终于到正餐啦!!!
函数对象与普通对象的区别:
函数对象和普通对象是有区别的,那我们怎么去分辨这种区别呢???
var o = {}; // object(普通对象)
console.log( o.prototype ); // undefined
console.log( typeof o ); // "obejct"
/*----------------------我是你的分割线-------------------------*/
var f = function(){}; // function(函数对象)
console.log( f.prototype ); // {......}
console.log( typof f ); // "function"
console.log( typof f.prototype ); // "object" 原型是一个普通对象
结论:
1. 函数对象的类型是 function
,普通对象的类型是 object
!!!
2. 函数对象 有 原型 ( prototype
),普通对象 没有 原型 prototype
的!!!
每声明一个函数,就会有一个产生一个原型,这个原型的 引用 就保存在 函数对象的 func.prototype
上面;
为什么要用原型?
回答这个问题前,还需要一点前置知识 。。。。。。
理解什么是 引用:
引用的概念出自于 C++
,引用 的实质是 指针,不过用 引用 来思考 javascript 中的对象我想更为合适;
首先,我们所声明的每一个变量都是会占用计算机资源的,它们被保存在计算机内存的某个位置;
引用也会占据空间,小到一个小小的数字,大到一个巨大的对象:
如图,所示,引用,就是 “外号” ,一个人可以有很多种外号,无论叫你哪一个外号,都是指你本身!!!
这是如何体现在代码里面呢?
小明,有两个外号,狗蛋 和 嘤嘤嘤;
小红,有两个外号,二狗 和 哈哈哈;
那么,小明是不是小红呢?小明和小红大家都很熟悉了,不是一个人;
var xiaoming = {name: '小明'}; // 我们用小明代指这个对象
var goudan = xiaoming; // 小明 有一个外号叫做 狗蛋
var yingyingying = xiaoming; // 小明 有一个外号叫做 嘤嘤嘤
console.log( goudan === xiaoming ); // true 狗蛋 是 小明
console.log( yingyingying === xiaoming ); // true 嘤嘤嘤 是 小明
/*------------我是分割线-----------------*/
var xiaohong = {name: '小红'}; // 我们用小红代指这个对象
var ergou = xiaohong; // 小红 有一个外号叫做 二狗
var hahaha = xiaohong; // 小红 有一个外号叫做 哈哈哈
console.log( ergou === xiaohong ); // true 二狗 是 小红
console.log( hahaha === xiaohong ); // true 哈哈哈 是 小红
/*----------------我是分割线---------------------*/
console.log( xiaohong === xiaoming ); // false 小明 不是 小红 <= 这个例子虽然无聊但是有用
很显然的,我们用 var
声明的变量就叫做 引用(别名) 我们通过这个 引用 来找到这个对象,在计算机里面的位置。
初次认识属性 __proto__
:
在回答为 什么要用原型 这个问题时,我们还需要明白一个属性 __proto__
。
我这里先不阐述 __proto__
属性具体的定义和概念,以免大家思维混乱 。。。。。。
var afunc = function (name) { // afunc就是构造函数!!!
this.name = name
this.get = function() {
return this.name;
};
}
afunc.prototype = {
set: function(newName) {
this.name = newName;
}
}
var robotA = new afunc('html');
console.log( robotA.prototype ); // undefined 前面文章已经解释了 普通对象没有 原型
console.log( robotA.__proto__ ); // {set: function() {...}} 找到了原型。
robotA.set('css');
console.log(robotA.get()); // "css"
现在你能够理解,robotA.__proto__
和 afunc.prototype
都仅仅是 afunc
的原型的 引用 了吗???
如果,不理解,再仔细想想,思考一下,在之后的内容里,我会展示 __proto__
更为详细的讲解;
当我们调用 robotA.get()
方法时,JS 会先去 构造函数 里面找是否有此方法,没有的话就到 原型 里面找!!!
回答为什么要用原型:
我们明白了什么是引用,就非常好解释,为什么需要用原型。
var afunc = function (name) { // afunc就是构造函数!!!
this.name = name
this.get = function() {
return this.name;
};
}
afunc.prototype = { // 调用原型对象
set: function(newName) {
this.name = newName;
}
}
var robotA = new afunc('html');
var robotB = new afunc('css');
var robotC = new afunc('javascript');
robotA === robotB // false 这两在计算机中的对象 是不一样
robotA === robotC // false
robotB === robotC // false
robotA.__proto__ === robotB.__proto__ // true 这两个引用所指的对象 是相同的
robotA.__proto__ === robotC.__proto__ // true
robotB.__proto__ === robotC.__proto__ // true
仔细想想,robotA
、robotB
、robotC
,这是分别是三个对象的 引用(别名);
计算机为这三个对象,分配了,三个存储空间,我们通过引用,找到这个存储空间的位置,然后执行!!!
而 robotA.__proto__
、robotB.__proto__
、robotC.__proto__
也是三个引用(别名);
但是,这上述三个引用指向的内容,都是 afunc
函数的 原型 ,计算机里只保存唯一的一个 afunc
函数的 原型 ;
这样的原型有什么好处???
如果,你需要 new
1000 个对象,这 1000 个对象就会分配 1000 个对象的存储空间;
试想,每次 new
相同的 属性和方法 ,这样系统的开销就会变得非常大!!!
我们把相同的属性和方法放在一起,抽象为一个原型,这 1000 个对象都访问原型上的 共有方法。
这样岂不是,美滋滋!!!
谈谈原型链:
终于到这一步了,感觉手都写酸了。。。。。
原型链,肯定跟原型有关啦,那这个关系到底是什么?
理解 __proto__
的本质:
每一个对象,不管是函数对象或者普通对象,都会有 __proto__
属性。
但是,这个属性已经 不推荐使用 了,但是为了展示什么是 原型链,我用了它。
如果想了解原因:请狠狠地戳这里,了解ES6标准;
var o = { };
console.log( o.__proto__ ); // {......} 生成 o 对象的原型;
console.log( o.__proto__.constructor ); // 生成 o 对象的构造函数 f Object() { };
console.log( o.__proto__.__proto__ ); // null; 到头啦,最初的起点 null !!!
关于 {......}
代表的是 object 起点原型,它也是一个普通对象!!!;
仔细思考,这两段代码,我不在这里阐述过多的东西,其实这里很绕的;
现在,来 函数对象 啦,仔细观察,此过程,比单纯的 普通对象 要复杂!!!
var f = function() { };
console.log( f.__proto__ ); // ƒ () { } 生成 f 函数对象的原型
console.log( f.__proto__.constructor ); // 生成 f 函数对象的构造函数 f Function() { }
console.log( f.__proto__.__proto__ ); // {......} 生成 f 函数对象的原型的原型
console.log( f.__proto__.__proto__.constructor ); // ƒ Object() { }
console.log( f.__proto__.__proto__.__proto__ ); // null 又到头啦!!!
如果,你理解了这段代码,也就理解了,为什么说,函数对象也是对象这句话了。
现在,我们来模拟一下 ,从 new
一个对象,开始的原型链过程 !!!
var f = function() { };
f.prototype = {
name: "javascript"
}
var obj = new f();
console.log(obj.__proto__); // { name: "javascript" } 构造 obj 普通对象的 原型
console.log(obj.__proto__.__proto__); // { ...... } 构造 obj 普通对象原型的 原型
console.log(obj.__proto__.__proto__.__proto__); // null 到头啦!!!
/*------------这是你需要区分的东西----------------*/
console.log(f.prototype); // f 函数对象的原型
console.log(obj.prototype); // undefined 普通对象没有原型!!!
其余的内容留给读者自己去思考。试着想一想,能否画出整个内置函数对象的原型链关系图
重要参考和感谢:
- https://www.cnblogs.com/shuiyi/p/5305435.html。
- https://www.jianshu.com/p/dee9f8b14771。
- 《你不知道的JavaScript 上卷》。
感觉不错,点个赞再走呗!!!
在此对上面链接或者书籍的作者表示感谢和支持,欢迎大家提出问题!!!