?? 个人主页 极客小俊
??? 作者简介: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"));
最后总结
而这个关系是通过
而也只有
那么有了
首先在
同时,我们也对
也就是说
