原型链
# 原型链
参考链接:https://www.cnblogs.com/xiaohuochai/p/5721552.html (opens new window)
当谈到继承时,JavaScript 只有一种结构:对象。
首先,每个js对象都会对应一个原型对象,并从原型对象继承属性和方法。
其次,_proto_
(隐式原型)的值就是它对应的原型对象。
然后,constructor
的值就是原型对象对应的构造函数。
**每个实例对象( object )都有一个私有属性(称之为 __proto__
)指向它的构造函数的原型对象(prototype )。**该原型对象也有一个自己的原型对象( __proto__
) ,层层向上直到一个对象的原型对象为 null
。
根据定义,null
没有原型,并作为这个原型链中的最后一个环节。
[TOC]
# 一、一张图的事
function Foo() {
}
Foo.prototype.getName = function () {
console.log(3);
};
var foo = new Foo();
foo.getName();//3
Foo.prototype.getName();//3
Foo.getName();//TypeError:Foo.getName is not a function
// 因为Javascript查找属性或方法的顺序是先查找对象本身然后查找对象的原型链
// 不会针对函数而查找函数的prototype属性的。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1.1 Foo.getName()找法
1.找本身 没有getName方法
2.找__proto__
,发现指向Function.prototype。
3.在Function.prototype上找,没找到
4.找在Function.prototype.__proto__
,发现指向Object.prototype
5.在Object.prototype上找没找到,去Object.prototype.__proto__找
6.发现没有Object.prototype.__proto__
7.结束
# 1.2 foo.getName()找法
1.找本身 没有getName方法
2.找__proto__
,发现指向Foo.prototype
3.在Foo.prototype上发现getName方法
4.执行
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const member = new Person("Lydia", "Hallie");
Person.getFullName = () => this.firstName + this.lastName;
console.log(member.getFullName()); // TypeError:member.getFullName is not a function
2
3
4
5
6
7
8
9
不能像使用常规对象那样向构造函数添加属性。 如果要一次向所有对象添加功能,则必须使用原型。 所以在这种情况下应该这样写:
Person.prototype.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
}
2
3
这样会使member.getFullName()
是可用的,为什么样做是对的? 假设我们将此方法添加到构造函数本身。 也许不是每个Person
实例都需要这种方法。这会浪费大量内存空间,因为它们仍然具有该属性,这占用了每个实例的内存空间。 相反,如果我们只将它添加到原型中,我们只需将它放在内存中的一个位置,但它们都可以访问它!
# 二、prototype
(显式原型)
- 不像每个对象都有
__proto__
属性来标识自己所继承的原型,只有函数才有prototype
属性。 - 每一个函数在创建之后都会拥有一个名为
prototype
的属性,这个属性指向函数的原型对象。( 通过Function.prototype.bind
方法构造出来的函数是个例外,它没有prototype
属性 )。
函数在JS中真的很特殊,是所谓的_一等公民_。JS不像其它面向对象的语言,它没有类(class
,ES6引进了这个关键字,但更多是语法糖)的概念。JS通过函数来模拟类。
当你创建函数时,JS会为这个函数自动添加prototype
属性,值是空对象。而一旦你把这个函数当作构造函数(constructor
)调用(即通过new
关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype
的所有属性和方法(实例通过设置自己的__proto__
指向承构造函数的prototype
来实现这种继承)。
实例对象Object和Foo本身没有constructor属性,需要继承原型对象Function.prototype的constructor属性。
作用
- 显式原型:用来实现基于原型的继承与属性的共享。
- 隐式原型:构成原型链,同样用于实现基于原型的继承。
# 三、new基本原理
// 模拟new原理
// func为构造函数
function new1(func, ...args) {
// 1.创建空对象
let obj = {};
// 2.将原型指向构造函数的原型
obj.__proto__ = func.prototype;
// 3.将this指向新实例,并执行构造函数的代码
let res = func.call(obj, ...args);
// 4.如果构造函数返回的是对象((包含 Functoin,Array,Date,RegExg,Error),则直接返回;否则返回创建的新对象
return (typeof res === 'object'|| typeof res === 'function')&& res ? res : obj;
}
// 检验
function Test(name, age) {
this.name = name;
this.age = age;
}
let test = new1(Test, 'Lin', 22);
console.log(test); // {name:'Lin',age:'22'}
console.log(test instanceof Test); // true
console.log(test instanceof Object); // true
console.log(test.__proto__.constructor === Test); // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PS:在不用传参的情况下,new test = new Test()。
# 四、原型、构造函数、实例及原型链
任何一个函数,如果在前面加了new,那就是构造函数。
- 已知A继承了B,B继承了C。怎么判断 a 是由A直接生成的实例,还是B直接生成的实例呢?还是C直接生成的实例呢?
- 用原型的constructor属性
foo.__proto__.constructor === Foo
的结果为true,但是foo.__proto__.constructor === Object
的结果为false。- 而用instanceof则不够严谨。
foo instanceof Foo
的结果为true,因为foo.__proto__ === Foo.prototype
为true。- foo instance of Objecet的结果也为true,因为
Foo.prototype.__proto__ === Object.prototype
为true。
# 五、原型的实际应用
# 5.1 zepto如何使用原型
(function (window) {
var zepto = {}
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
zepto.Z = function (dom, selector) {
return new Z(dom, selector)
}
zepto.init = function (selector) {
var slice = Array.prototype.slice
var dom = slice.call(document.querySelectorAll(selector))
return zepto.Z(dom, selector)
}
var $ = function (selector) {
return zepto.init(selector)
}
window.$ = $
$.fn = {
css: function (key, value) {
alert('css')
},
html: function (value) {
return '这是一个模拟的 html 函数'
}
}
Z.prototype = $.fn
})(window)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
把原型方法放在$.fn,可以方便扩展插件
$.fn.getNodeName = function() {
return this[0].nodeName;
}
2
3
只有$会暴露在window全局变量。
将插件扩展统一到$.fn.XXX这一接口,方便使用。
# 5.2 jQuery如何使用原型
(function (window) {
var jQuery = function (selector) {
return new jQuery.fn.init(selector)
}
jQuery.fn = {
css: function (key, value) {
alert('css')
},
html: function (value) {
return 'html'
}
}
var init = jQuery.fn.init = function (selector) {
var slice = Array.prototype.slice
var dom = slice.call(document.querySelectorAll(selector))
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
init.prototype = jQuery.fn
window.$ = jQuery
})(window)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32