?? 个人主页 极客小俊
??? 作者简介:web开发者、设计师、技术分享博主
?? 希望大家多多支持一下, 我们一起进步!??
?? 如果文章对你有帮助的话,欢迎评论 ??点赞???? 收藏 ??加关注
前言
有些新手朋友可能听说过这么一句话,就是
它们彼此的区别在于
而
其中这里的
我们学习
如果你没有搞明白
那么接下来就跟着我一起开始学习吧!
为什么要使用原型?
因为我们
如果你还没有完全明白
构造函数模式
有的时候我们会使用
虽然这种
这里我们来看一段简单的
function Person(username,age,job){ this.username=username; this.age=age; this.job=job; this.say=function (){ console.log('我的名字叫:'+this.username+',年龄:'+this.age+',职业:'+this.job); } } var test=new Person('张三',18,'ui设计师'); test.say();
这就是一段非常简单的
构造函数模式的缺点
任何一种技术的出现,都是为了弥补旧技术的不足!
那么
这里我先卖个关子,想知道的话就继续往下看吧! ??????
从上面的代码角度上看,确实感觉不出什么奇怪之处!
那我们再来看一个
//构造函数 function Person(){ this.say=function (num){ console.log('测试方法'+num); } } //实例化 var obj_1=new Person(); var obj_2=new Person(); //调用 obj_1.say(1); obj_2.say(2); console.log(obj_1.say===obj_2.say); //返回false
然后我们看看下面这张图你就知道其中缺点在什么地方了!
此时你会发现,这个函数居然会不相等!! 为什么呢?
从另一个角度来讲
要知道在
之所以会返回
并且函数本身也是对象, 你定义一个函数,也就相当于实例化了一个
上面的这行代码
this.say=function (num){ console.log('测试方法'+num); }
其实从一定逻辑上讲也可以看成以下形式
this.say=new Function (num){ console.log('测试方法'+num); }
这样子其实你更好理解每个
所以说像这种在内存中无限创建很多完成同样方法的
那么有没有什么好的方案可以解决这个问题呢?
我们完全可以使所有的
通常是可以把这个
function Person(username,age,city){ this.name=username; this.age=age; this.city=city; this.say=say; /*this.say=function() { console.log(this.name+'的年龄是:'+this.age); }*/ } //把say方法写在全局作用域中 function say() { //这里的this要清楚是谁在调用say这个方法 this自然就指向谁 console.log(this.name+'的年龄是:'+this.age); } var test1=new Person('张三',33,'北京市'); var test2=new Person('李四',66,'深圳市'); //判断test1与test2之间的方法是否是共用的 console.log(test1.say==test2.say);
以下案例也是同样的道理!
//构造函数 function Person(){ this.say=say; } //定义到全局下 function say(num){ console.log('测试方法'+num); } //实例化 var obj_1=new Person(); var obj_2=new Person(); //调用 obj_1.say(1); obj_2.say(2); console.log(obj_1.say===obj_2.say); //返回true
目的我们是达到了,但是这样写就真的行了吗? 不会存在其他问题吗?
那么如何来解决这样的问题呢? 就是接下来我要说的
原型的定义
我们说在
这个属性指向一个
而这个
我们来看一段简单的代码:
function Test(){ } console.log(Test.prototype); var T1=new Test(); var T2=new Test(); var T3=new Test(); console.log(T1.__proto__); console.log(T2.__proto__); console.log(T3.__proto__);
1.png
这里还要给大家科普一个小知识,就是普通对象没有
我们来验证一下
//普通对象 var json={} console.log(json.prototype); //元素对象 var oDiv = document.getElementById("connent"); console.log(oDiv.prototype); //函数对象 function test(){ } console.log(test.prototype);
1-0-1.png
所以说首先只有
理解构造函数、实例化对象、原型对象彼此之间的关系
这样就形成了每个
这里我们要说明一点的就是,只有
同时从图中也可以看到
我们一般会拿这个
如果你在一个
2-2.jpg
如果按照这个逻辑推理的话,你可以使用以下代码进行验证一下,是否正确:
console.log(T1.__proto__==Test.prototype); //返回true console.log(Test.prototype.constructor==Test); //返回true
结果证明的确是这样子,
其实你也可以使用
//构造函数1 function Person(name,age) { } var p1=new Person(); //构造函数2 function Test(){ } var test=new Test(); console.log(Person.prototype.isPrototypeOf(p1)); //返回true console.log(Person.prototype.isPrototypeOf(test)); //返回false
以上我们用了
在
console.log(Object.getPrototypeOf(p1)==Person.prototype); //返回true console.log(Object.getPrototypeOf(p1)==Test.prototype); //返回false
所以从结果上看
使用这个
同时
这样
并且这条链条,从图中我们也可以看到,还可以往上走到一个叫
实例对象属性和方法搜索的优先级
上面说了,有了
那么问题来了,这些
function createPerson(name,age) { this.name=name; this.age=age; this.say=function (){ console.log('2.构造函数中定义的say方法!'); } } createPerson.prototype.say=function () { console.log('我的名字叫:'+this.name); } var a=new createPerson('张三','33'); var b=new createPerson('李四','55'); var c=new createPerson('王武','66'); c.say=function(){ console.log('1.实例对象c 定义的say方法!'); } a.say(); b.say(); c.say();
结果如下:
4.png
按照这个查找逻辑上来看的话,调用的查找方式如下:
先在
注意:这并不是把
function Person(){ } Person.prototype.username='张三'; Person.prototype.age=30; Person.prototype.job='设计师'; Person.prototype.say=function (){ console.log('我是'+this.username); } var p1=new Person(); var p2=new Person(); p1.username='李四'; console.log(p1.username); console.log(p2.username);
首先这里构造函数中我们什么都没有定义的情况下,这里就是先搜索
如果没有找到,则会根据一个叫
那么这个案例中,则执行了两次搜索!先询问了
所以说我们在
而有了这个搜索模式的帮助下,
4-1.jpg
这里我再次提一嘴,前面不是使用到了
所以大家也应该注意一下,就是如果你在实例对象上定义了一个属性或者方法,而且原型对象中也定义了同名的属性或者方法,依照查找的顺序会依次搜索
也就是说当你在
即便是你在
但是如果你使用
function Person(){ this.username='李四'; } Person.prototype.username='王五'; Person.prototype.age=30; Person.prototype.job='设计师'; Person.prototype.say=function (){ console.log('我是'+this.username); } var p1=new Person(); p1.username=null; delete p1.username; console.log(p1.username); //这里输出的结果来自于 原型对象
其实我们就可以按照这个查找逻辑,来修改
__proto__的真正含义!
那么
这其实就要说到刚刚我们提及到的
我们来看一张图:
5.png
每个
而且
这就是我马上要提到的
特别注意
这里我提醒一下,可能你以前看到的也的确是叫
但是目前
6.png
这里只是显示变了而已,代码层面上,
然而
因为
prototype与__proto__的区别
其实我们在上面的图中也能看出来彼此的一个很明显的区别:
这里我特别提一下,其实
所以说大家不要再把
原型链
理解了以上这些是什么之后,那么接下来,我们就可以来研究一下什么是
并且
在学习
我们之前不是说了
就是说如果当你调用一个属性或者方法时, 其实首先是会在当前
那么有人就会问了,如果 当前构造函数的原型对象中也没有呢? 接下来
揭晓谜底吧,请看下图:
7.jpg
以上这张图就是默认情况下,如果说
那么找谁呢? 这是
并且既然这里有一个所谓的
8.png
所以这里 其实就是解释了
并且在
那我们要
所以我们需要
原型对象中的this指向
当我们使用
而
function Test(name,age,company,salary){ this.username=name; this.age=age; this.company=company; this.salary=salary; console.log(this);//打印this } Test.prototype.say=function (){ console.log(this); //打印this } var test=new Test('张三',18,'重庆科技','9K'); //打印实例对象 console.log(test);
8-1.png
由此可见
原型链继承的实现
在
我们废话不多说,直接看个案例!
//猫类 function Cat(){ this.username='小猫'; } //狗类 function Dog(){ this.username='小狗'; } //老虎类 function tiGer(){ this.username='老虎'; } //猫类的原型对象中有一个方法 Cat.prototype.behavior=function (){ console.log('【'+this.username+'】 这种动物真的会要咬人!!....'); console.log(this);//谁调用this归谁! } //实例化猫类 var cat=new Cat(); //把狗类的原型对象指向猫 Dog.prototype=cat; //实例化狗类 var dog=new Dog(); //把老虎的原型对象指向狗 tiGer.prototype=dog; //实例化老虎类 var tiger=new tiGer(); //调用方法 tiger.behavior(); dog.behavior();
这里我们修改了
那么这样一来会造就什么样的情况呢?
简单一点说, 我们就会顺着一个:
也就是
这里也很明显,
9.jpg
当然你也可以通过修改
//猫类 function Cat() { this.username = '小猫'; } //狗类 function Dog() { this.username = '小狗'; } //老虎类 function tiGer() { this.username = '老虎'; } //猫类的原型对象中有一个方法 Cat.prototype.behavior = function () { console.log('【' + this.username + '】 这种动物真的会要咬人!!....'); console.log(this);//谁调用this归谁! } //实例化猫类 var cat = new Cat(); //实例化狗类 var dog = new Dog(); //实例化老虎类 var tiger = new tiGer(); //修改原型链指针 tiger.__proto__= dog; dog.__proto__= cat; //console.log(tiger); tiger.behavior();
首先,定义了三个构造函数:
接下来,在
然后,通过实例化
这样就建立了
接着,通过实例化
最后,通过实例化
由于原型链上的继承关系,调用这个
当然如果这里再调用
这样就形成了一个
毕竟
通过
最后会指向我们
这样一直可以
在这个程中就形成了
我们也可以使用
console.log(tiger);
10.png
这里如果眼尖的朋友可能已经注意到了一个问题,那就是
我们可以用以下代码测试一下:
console.log(tiGer.prototype.constructor); console.log(Dog.prototype.constructor);
10-1.png
如果你想看上去比较合理一点,加入以下代码
Dog.prototype.constructor=Dog; tiGer.prototype.constructor=tiGer;
10-2.png
这就是
有些
所以说
给大家专门准备了一张通用默认
11.jpg
基于原型链的继承
看了以上的案例和图例之后,我们应该就对
对象属性的继承
但是有一点我觉得值得注意,就是修改
var obj={ a: 值1, b: 值2, __proto__: c }
比如在像这样的
我们来看个实际的小案例
const obj = { a: '张三', b: '李四', __proto__: { b: "王五", c: "绿巨人", }, }; console.log(obj); console.log(obj.a); delete obj.b; console.log(obj.b); console.log(obj.c);
当前obj的原型链中具有属性 b和c两个属性
如果
最后
完整的原型链看起来像这样:
那么要说继承关系的话,那就是
即便是这里我使用了
11-0-1.png
但是注意了如果这里我没有使用
当然我们也可以根据这个原理来创建更长的
const obj = { a: 1, b: 2, // __proto__ 设置了原型链。它在这里被指定为另一个对象字面量。 __proto__: { b: 3, c: 4, // __proto__ 设置了原型链。它在这里被指定为另一个对象字面量。 __proto__: { d: 5, }, }, }; console.log(obj.d); // 输出5
那么它的原型链就是如下这样:
其实就是这样就可以嵌套很多层出来,让对象看起来更加有层次结构,也方便管理一些特殊的数据!
对象方法的继承
特别要说明的其实也就是
const parent = { username: '张三', age:35, method() { return '我的年龄是'+(this.age + 1)+'岁'; } } console.log(parent.method()); //输出36 //然后我们通过child继承了parent的对象 const child = { __proto__: parent, } console.log(child.method());//输出36 child.age = 5; console.log(child.method());
当调用
然后我们通过
现在调用
首先在
然后我们在
最后输出:
function Test() { this.username = '张三'; this.age = 33; this.job='软件开发'; } Test.prototype.say=function (){ return '我的名字叫:'+this.username+',我的年龄是:'+this.age+'我的职业是:'+this.job; } var test=new Test(); //新建一个对象,并且修改原型链 var obj = { username:'李四', age:'35', __proto__:test } console.log(obj); console.log(obj.username); console.log(obj.age); console.log(obj.say());
另类继承实现方法
修改构造函数this指向从而实现继承
我们有时候可以借助
function Animal(name,age,food){ this.username=name; this.age=age; this.eat=function (){ console.log('这只['+this.username+']动物要吃['+food+']'); } } Animal.prototype.color='黑色'; Animal.prototype.say=function (){ console.log('我的名字叫'+this.name); } function Panda(name,age,eat){ this.like='玩耍'; Animal.call(this,name,age,eat); //借用一下 } var p1=new Panda('熊猫盼盼',18,'竹叶'); //打印输出 console.log(p1); p1.eat();
11-0-2.png
这个案例中,应用了
但是这种使用
因为这种方式有一个很大的缺点,就是不能
所以这种
//定义构造函数 function Person(userename,age,sex){ this.name=userename; this.age=age; this.sex=sex; this.type='人类'; } Person.prototype.say=function(){ console.log("hello world"); } function Student(username,age,sex,score){ //借用Person构造函数 Person.call(this,username,age,sex); //定义属性 this.score=score } //改变原型指向 Student.prototype=new Person();//不传值 Student.prototype.behavior=function(){ console.log("英语学习!!"); } var s1=new Student("张三",15,"男","100分") //打印结果看看 console.log(s1); console.log(s1.type); console.log(s1.name); console.log(s1.age); console.log(s1.sex); console.log('考试得分:'+s1.score); s1.behavior(); s1.say();
从上面的代码中,我们可以看到
这时都可以通过
11-0-3.png
通过循环复制实现继承
我们的
function Person(username,age) { this.name=username; this.age=age; } Person.prototype.type = "人类"; Person.prototype.nationality = "中国"; Person.prototype.job = '软件开发'; Person.prototype.like = '足球,篮球,游戏'; Person.prototype.test = 123; function Student(username,age) { Person.call(this,username,age); } var per = Person.prototype; var stu = Student.prototype; //过滤不需要的属性 var arr=['type','nationality','test']; for (k in per) { if(arr.indexOf(k)==-1){ stu[k] = per[k]; } } var s1=new Student('李四',18); var s2=new Student('王五',25); console.log(s1); console.log(s2);
11-0-4.png
__proto__ 的兼容性
根据
那么到底我们平常使用什么来修改原型的指针呢?
在
11-1.png
所以如果可以的话我们尽量不使用
让我们使用
function Test(){ } Test.prototype.company='重庆科技'; function Test2(){ } Test2.prototype.num=100; function Test3(){ } Test3.prototype.username='张三'; //实现继承 Object.setPrototypeOf(Test2.prototype, Test.prototype); Object.setPrototypeOf(Test3.prototype, Test2.prototype); var t3=new Test3(); console.log(t3);
11-2.png
再看一个案例!
const a = { company : '重庆科技' }; const b = { age: 33 }; const c = { username: "张三" }; //实现继承 Object.setPrototypeOf(a, b); Object.setPrototypeOf(b, c); console.log(a); console.log(a.username); console.log(a.age); console.log(a.company);
11-3.png
所以我觉得可以的情况下,尽量使用标准的
因为
原型链与继承查找机制
当你访问一个对象的
所以当你把之前的
DOM原型链的形成
这其实也很好的解释了我们
<div id="connent"></div>
var oDiv = document.getElementById("connent"); console.log(oDiv.__proto__); console.log(oDiv.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__);
12.png
其实这样你就会知道当一个
↓HTMLDivElement ↓HTMLElement、 ↓Element、 ↓Node、 ↓EventTarget ↓Object
通过
这里也给大家简单介绍一下,方便理解!
-
HTMLDivElement : 这是一个代表 HTML<div> 元素的类, 它继承了HTMLElement 的属性和方法,包括可以用来改变元素样式的属性和方法,当然这里我只是举个栗子,不一定就是div元素 根据你打印的情况决定! -
HTMLElement : 这是一个基础类,代表任何 HTML 元素, 所有的HTML 元素都继承了HTMLElement 的属性和方法 -
Element : 这是一个基础类,代表任何HTML 或 XML 元素, 它定义了所有元素共享的属性 和方法 ,例如getAttribute() 和setAttribute() -
Node : 这是所有DOM 节点的基类,包括元素、文本节点、注释 等, 它定义了一些通用的属性 和方法 ,如parentNode 和childNodes 。 -
EventTarget : 这个接口表示可以添加或删除事件监听器的事件目标 -
Object : 这个也就是顶层的Object构造函数
在w3c也有这些属性和方法的详细解释
13.png
而且这些
当查找
并且
以上这些
其实你可以去通过
// 创建一个新的XML文档 var xmlDoc = document.implementation.createDocument(null, null); // 创建根元素 var root = xmlDoc.createElement("root"); xmlDoc.appendChild(root); // 创建一个子元素 var child = xmlDoc.createElement("child"); // 设置子元素的内容 var childText = xmlDoc.createTextNode("This is a child element"); child.appendChild(childText); // 将子元素添加到根元素 root.appendChild(child); // 打印XML文档 console.log(xmlDoc.__proto__); console.log(xmlDoc.__proto__.__proto__); console.log(xmlDoc.__proto__.__proto__.__proto__); console.log(xmlDoc.__proto__.__proto__.__proto__.__proto__); console.log(xmlDoc.__proto__.__proto__.__proto__.__proto__.__proto__); console.log("------------------------------------------------------------"); var oDiv = document.getElementById("oDiv"); console.log(oDiv.__proto__); console.log(oDiv.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__); console.log(oDiv.__proto__.__proto__.__proto__.__proto__.__proto__); console.log("------------------------------------------------------------"); console.log(document.__proto__); console.log(document.__proto__.__proto__); console.log(document.__proto__.__proto__.__proto__); console.log(document.__proto__.__proto__.__proto__.__proto__); console.log(document.__proto__.__proto__.__proto__.__proto__.__proto__); console.log("------------------------------------------------------------"); // 创建一个新的SVG文档 var svgNS = "http://www.w3.org/2000/svg"; var svgDoc = document.implementation.createDocument(svgNS, "svg", null); // 添加根元素 var root = svgDoc.documentElement; // 添加一个矩形元素 var rect = svgDoc.createElementNS(svgNS, "rect"); rect.setAttribute("x", 10); rect.setAttribute("y", 10); rect.setAttribute("width", 100); rect.setAttribute("height", 100); rect.setAttribute("fill", "blue"); root.appendChild(rect); // 添加一个圆形元素 var circle = svgDoc.createElementNS(svgNS, "circle"); circle.setAttribute("cx", 120); circle.setAttribute("cy", 120); circle.setAttribute("r", 50); circle.setAttribute("fill", "red"); root.appendChild(circle); // 将SVG文档添加到HTML文档中 document.body.appendChild(root); console.log(rect.__proto__); console.log(rect.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); console.log(rect.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__);
然后看看他们的
14.jpg
使用原型和原型链的好处
到这里学了那么多,我们使用
其实
现在有一个
比如说现在有多个
const arr = [ { value: '张三', getValue() { return this.value; } }, { value: '李四', getValue() { return this.value; } }, { value: '王五', getValue() { return this.value; } }, ];
但是你可以想一下,这样子做好吗? 每一个对象都基本上有同样的代码, 这就是
所以你可以尝试优化一下,当然优化的办法有很多,这里我们重点讨论的就是
你可以试想一下将
那么我们加以修改一下,变成如下形式
//公共使用 const _ObjPublic={ getValue() { return this.value; } } const arr = [ { value: '张三', __proto__:_ObjPublic }, { value: '李四', __proto__:_ObjPublic }, { value: '王五', __proto__:_ObjPublic }, ]; console.log(arr[0].getValue()); console.log(arr[1].getValue()); console.log(arr[2].getValue());
optimize-1.png
这样一来所有对象中的
但是上面这样一个一个手动去捆绑
这时,我们就可以使用
因为当我们使用
其实在
- 优化和简化代码,实现代码重用
- 根据
__proto__ 的链条实现属性和方法 的继承 ,只要是这个__proto__ 链条上的东西,都可以被调用到
所以在
我们单纯的来说一下
所以为了避免了代码冗余,公共使用的
构造函数名.prototype.属性=值; 构造函数名.prototype.方法=function(){ ..代码段.. }
然后通过
也就是说这个类型的
这样做的一个好处是:
function createPerson(name,age) { this.name=name; this.age=age; } createPerson.prototype.say=function () { console.log('我的名字叫【'+this.name+'】, 我的年龄是:'+this.age); } var a=new createPerson('张三','33'); var b=new createPerson('李四','55'); a.say(); b.say(); console.log(a.say===b.say);
1-1.png
大家可以看到,
并且这样子做就相当于所有的
这样做的好处,在于节约
我们来看下面这张图:
2.png
这就是让公用的
而不管我们实例化多少次对象出来,
也就是说只要是通过
2-1.jpg
所以我们给
当然你也可以把所有的
相当于
你完全可以使用
console.log(Object.getPrototypeOf(a) === createPerson.prototype); //返回true console.log(Object.getPrototypeOf(b) === createPerson.prototype); //返回true console.log(createPerson.prototype.constructor === createPerson); //返回true
字面量与原型链之间的关系
在
这里我给大家举几个案例就会明白了~~
对于使用对象字面量创建的对象,
你也可以理解为对象字面量没有
var obj = {} console.log(obj.__proto__); console.log(Object.getPrototypeOf(obj) === Object.prototype); //返回true
对于使用数组字面量创建的对象,
也就是说
var arr = []; console.log(arr.__proto__); console.log(Object.getPrototypeOf(arr) === Array.prototype); //返回true
如果是
const regexp = /abc/; console.log(Object.getPrototypeOf(regexp) === RegExp.prototype) // true
对于使用
如果是
var str = ""; console.log(str.__proto__); console.log(Object.getPrototypeOf(str) === String.prototype) // true
如果是使用
var num = 100; console.log(num.__proto__); console.log(Object.getPrototypeOf(num) === Number.prototype) // true
那么如果是函数呢,一个
那
也就是会自动将
var fn = function () { } console.log(fn.__proto__); console.log(Object.getPrototypeOf(fn) === Function.prototype) // true
所以说这又解释了为什么有些
比如像
性能与原型链
了解
因为
所以说我们在遍历对象的属性时,最好先判断一下 要检查对象是否具有在其
function Graph() { this.vertices = []; this.edges = []; } Graph.prototype.addVertex = function (v) { this.vertices.push(v); }; const g = new Graph(); // 当前原型链为: g ---> Graph.prototype ---> Object.prototype ---> null //检查对象自身是否有vertices属性 返回true console.log(g.hasOwnProperty("vertices")); //检查对象自身是否有nope属性 返回false console.log(g.hasOwnProperty("nope")); //检查对象自身是否有addVertex属性 返回false console.log(g.hasOwnProperty("addVertex")); //检查原型对象自身是否有addVertex属性 返回true console.log(Object.getPrototypeOf(g).hasOwnProperty("addVertex"));
最后总结
而这个关系是通过
而也只有
那么有了
首先在
同时,我们也对
也就是说