在前端学习中 ,我一直都是两个概念闹不明白;
1,闭包。(闭包是啥?为啥要用?怎么用?有些教程很好;而有些教程高深的机器的到望而生畏);
2,原型链。(啥是原型链?谁是谁的原型对象?谁是谁的?你是你的我是我的?还是我的是我的你的还是我的?*¥%……@#¥);
闭包是啥?
好吧,想了解闭包的同学请去我搬砖过来的文章,传送门:https://blog.csdn.net/wangrong111222/article/details/79938999
下面我们来进行消化一下原型链:
在此之前我要讨论讨论三种造对象的简单模式(我这个人喜欢简单点阐述道理,毕竟万物生于null; 手动滑稽 );
1,字面量模式(设计模式:单例模式)
var monkey={
name:"wukong",
eye:"fireGoldEyes",
attack:function(){
alert(this.name+"一棒造成了成吨的伤害")
}
}
好好好,我们用字面量模式造了一只猴子,这只猴子叫做孙悟空,并且带了火眼金睛;并且让他可以造成了成吨的伤害;
这种方式应该是我们最经常用的对象定义方式,也是比较推荐的定义方式,因为他比其他的方法更加运行效率高;而且清楚明了;
弊端 :每次都要写一个对象,伴随着需求增加到最后实际的会有成吨的对象,不够灵活,还不能量产;因此第二种方法就应运而生;
2, 工厂模式(设计模式:工厂模式)
function createObj(name,eye,attcak){
var o=null;
o.name=name;
o.eye=eye;
o.attack=attcak
return o;
}
var monkey =createObj("wukong","fireGoldEyes",function(){
alert(this.name+"一棒造成了成吨的伤害");
})
这种模式叫做工厂模式;顾名思义 ,工厂流水线生产,可量产;这种模式的是解决了字面量模式的问题,可是有引申出了新的问题:我们虽然获得了很多的对象,但是对于这些个对象去追本溯源,无从找起;你可以理解为工厂老板没有为每一个产品写详细的记录(不负责任的老板);在某些我们希望去查找到并且从源头去修改属性的场景先得比较不给力;so,官方又给出了新的方法来补充;
3,构造函数 (设计模式:构造函数模式)
function Monkey(){
this.name="wukong",
this.eye="fireGoldEyes";
this.attack=function(){
alert(this.name+"一棒造成了成吨的伤害")
}
};
var sunxingzhe=new Monkey();
jsz中构造函数的实现借鉴了其他oo语言模式的实现,函数名首字母大写,区别于js里其他的函数;在没有被new操作符之前 ;和其他的函数都是一样的一样的;new之后 ,就有了黑魔法可以造对象了;
比较工厂模式右以下不同:
1,没有显示的创建对象;
2;没有return 啥玩意出来(1,2可以理解为后台自己封装操作了);
3;直接将函数的属性和方法赋给了 当前的的this的对象;
在执行new操作后的话执行以下会有以下的过程:(面试题哦,手动滑稽)
1:创建一个对象;
2;将函数作用域上赋给了 当前的的新的对象;
3,执行一遍构造函数里的代码,给新对象赋值(方法貌似会自己跑一遍);
4,返回新的对象;
生成新的对象的构造器指向了生成他的构造函数
(console.log( sunxingzhe.constructor=Mokey) //true)
,你可以理解为孙行者是猴子这个构造函数生的);当然 用instanceof这个方法来检测也是可以的
(console.log( sunxingzhe instanceof Mokey) //true);
ps:像Object,Array等这种也是原生的构造函数哦;
这样的话我们就可以找到造孙悟空的 机器了,知道了是谁造的并且可以修改上面的属性和方法,一切变得可控,但是同时他会有一个新的问题;
function Monkey(){
this.name="wukong",
this.eye="fireGoldEyes";
this.attack=function(){
alert(this.name+"一棒造成了成吨的伤害")
}
};
var sunxingzhe=new Monkey();
var liuermihou=new Monkey();
现在我们重新造一个新的大圣出来---六耳猕猴;
console.log(sunxingzhe.attack===liuermihou.attack) //false;
我们会发现这每个对象在被new出来以后都会拥有一个不同的方法(方法每次都重新new Function(),内存中引用的地址不一样);我们可以尝试如此改进:
function Monkey(){
this.name="wukong",
this.eye="fireGoldEyes";
this.attack=attackFun;
};
function attackFun(){
alert(this.name+"一棒造成了成吨的伤害")
}
var sunxingzhe=new Monkey();
var liuermihou=new Monkey();
以上的代码改进主要两点优势:
(1);把公共的方法定义到了window上,使得所有new的实例都是引用的window上的这个方法;提升了效率;
(2);对象只有在调用的时候才回去寻找function的实际地址;而不是像之前的时候每次在new的时候就去定义了一遍 ,从而提升了效率;
这样改的弊端是:
1,window的命名空间被污染,私有方法暴漏在公共区域,合适吗?
2,如果存在很多的方法,那么是不是一种灾难?
在此基础上;今天的主角登场:
原型链 以及__proto__
首先我们来说明几个概念;
* , __proto__代表了原型链的指向;prototype(原型对象)也是一种指向,并且指向一个对象,拥有实例共享的方法属性;
1, 所有的对象都拥有__proto__(万物皆对象);
2,函数同时拥有prototype和__proto__;
3, __proto__是一种指针,代表了一种指向(指向的意思是指没有直接关系,但是有间接关系;);
4,所有的__proto__会最终指向Object.protype的__proto__,值为null(万物生于null);
5, 构造函数生成的对象实例的__proto__指向向了一个对象;这个对象里包含一个constructor(构造器)属性和各种从构造函数继承过来的属性方法;(
这里的sunxingzhe.__proto__.constructor===sunxingzhe.constructor;
__proto__在对象里寻找构造器似乎可以省略;直接.constructor就能找到;)
同时 对象实例的__proto__指向构造函数的prototype; sunxingzhe.__proto__===Monkey.prototype; // true
对象实例的constructor指向了构造函数;sunxingzhe.__proto__.constructor===Monkey// true
上面这条也可以这样解释
sunxingzhe.__proto__.constructor===Monkey===Monkey.prototype.constructor// true
三者存在相等关系,都是指向了Monkey;
6;构造函数本身的protype里也有constructor属性;指向他自己;
所谓的 Monkey.prototype.constructor===Monkey //true;
我们console.dir(Monkey);在控制台去打印Monkey本身的时候,看他的prototype.constructor属性 会一直循环找下去;
也是因为这个道理; 另外自己的__proto__最终指向了Object;从而最终指向null;
Constructor ----> Function.prototype ----> Object.prototype ----> null
上述代码表示了原型链的关系;
这个方法叫做 原型模式(设计模式:原型模式)
function Monkey(){
}
Monkey.prototype.name="wukong";
Monkey.prototype.eye="fireGoldEyes";
Monkey.prototype.attack=function(){
alert(this.name+"一棒造成了成吨的伤害")
};
var sunxingzhe=new Monkey();
以上代码就是一个原型模式的实现;我们可以看到;所有要生成的的属性实例都被放在了protype属性中;这样之前构造函数面临的公共命名空间被污染的问题也就解决了,这边所有的实例都会走prototype上的方法;
而检测实例是否属于构造函数的 方法有:
1;Monkey.prototype.isPrototypeOf(sunxingzhe);//看函数的原型对象是否包含这个实例的__proto__指向;
2; Object.getPrototypeOf( sunxingzhe)===Monkey.prototype; // 拿到实例的__proto__和构造函数的prototype做比较;ES5新方法;
我们在调用实例的方法或者属性的时候,解析器会先去寻找实例上是否存在,存在就读自己本身的属性;否则沿着原型链向上一级一级查找;直到null(万物生于null的那个null);
我们假如需要增加某条 或者修改某条属性;可以这样;
sunxingzhe.age=1000000;
sunxingzhe.name="bimawen";
上述的修改模式只是自己发生变化,不会影响到构造函数,构造函数已有的属性会被浅浅的覆盖掉;
delete操作符可以删除实例上的属性但是不会动到原型链的上游属性;(注意不管有不有该属性delete都会true);
如果要检测一条属性是否属于原型的方法是 hasOwnProperty( );//返回属性源头是实例才会返回true,比如说
sunxingzhe.hasOwnProperty("name") // "bimawen" true; name目前来源于实例
sunxingzhe.hasOwnProperty("eye") // "fireGoldEyes" false; name目前来源于构造函数
sunxingzhe.hasOwnProperty("attack") // "bimawen一棒造成了成吨的伤害" name目前来源构造函数
sunxingzhe.hasOwnProperty("age") // "1000000" true name目前来源于实例
delete sunxingzhe.name //删除实例上的属性name 移除了实例上name属性的浅浅的覆盖
sunxingzhe.hasOwnProperty("name") // "wukong" false name目前来源于构造函数
in操作符;
"name" in sunxinfgzhe //true 如果这个属性或者方法存在实例或者原型链上那么 返回true
in 配合 hasOwnProperty 可以判断属性是在那个位置;
首先 in 判断是否右这个属性 ;
在判断是否是 原型链还是实例属性;
!sunxingzhe.hasOwnProperty("name")&& "name" in sunxinfgzhe //这样就判断出了 只存在原型链上的属性方法;
更加简单的原型方法
function Monkey(){
}
Monkey.prototype={
name:"wukong",
eye:"fireGoldEyes",
attack:function(){
alert(this.name+"一棒造成了成吨的伤害")
}
}
var sunxingzhe=new Monkey();
注意这样干的话 ,那么说当前的生成的实例constructor不再指
向Monkey构造函数的prototype,
而是指向了Object对象构造函数;
原因在于重写了构造函数的prototype,
导致实例constructor改变了指向;
如果在这里的实例的constructor
很重要;那么需要重设会原有构造函数即可;
function Monkey(){
}
Monkey.prototype={
constructor:Monkey,
name:"wukong",
eye:"fireGoldEyes",
attack:function(){
alert(this.name+"一棒造成了成吨的伤害")
}
}
var sunxingzhe=new Monkey();
不过这样的话会导致 constructor
属性可以被枚举;
而在原生是不可被枚举的;
所以需要通过 defineProperty的
属性进行设置
[[Enumerable]]
constructor属性为false;
实例被new出来以后,属性是和母体(构造函数)
的属性相通的; 构造函数发生改变,那么 实例上的属性也会发生改变
;原因归结为实例和原型之间的松散关系,(引用关系);
如果重写了构造函数的protype那么,
之前new出来的实例和当前重写以后的
原型对象没有半毛钱关系;之前new出来的实例还是
跟着之前的原型对象跑;(
虽然之前的原型看不到了但是有引用关系
,应该是没有被释放回收,所以的话是拿到了);
比较简单 一般也不用 就不拉代码了;
!!!
原生的引用类型都是采用这种模式创建的
,在他们的prototype上绑定一些方法;
Array.prototype上有splice方法;
也可以原生的引用类型上增加
自己的方法,不过,不推荐啊,原因自己想
原生模式的缺点;
1,不能入参进去动态初始化;
2.属性如果是引用类型的话;
如果实例修改了这个实例;会直接改到原型对象
上的引用数据类型;比如数组 实例去操作了这
个数组属性 那么改变到了原型对象上的实际引用;
基于此
完美方法: 构造函数+原型模式
业界认同度最高的方式:
1,可以入参动态的修改属性,
2,最大程度上那个节省了 定义方法的内存
3,解决了修改引用数据类型属性的bug
function Monkey(name,eye){
this.name=name,
this.eye=eye;
};
Monkey.prototype.attack=function(){
alert(this.name+"一棒造成了成吨的伤害")
}
var sunxingzhe=new Monkey("wukong",["fireGoldEyes","KM of eyes "]);
参考---红宝书