- 显式原型和隐式原型,原型链。
显式原型:prototype,隐式原型:__proto__在js中万物皆对象,方法(Function)是对象,方法的原型(Function.prototype)是对象,对象具有属性(__proto__)称为隐式原型,对象的隐式原型指向构造该对象的构造函数的显式原型。
方法(Function)是一个特殊的对象,除了和其他对象一样具有__proto__属性以外,它还有一个自己特有的原型属性(prototype),这个属性是一个指针,指向原型对象。原型对象也有一个属性叫constructor,这个属性包含一个指针,指向原构造函数。
显示原型和隐式原型的关系:隐式原型指向创建这个对象的函数的prototype,创建对象的三中对象:
a.通过对象字面量的方式。
var person={
name:"Tom"
}b.通过new的方式创建
//创建一个构造函数
function person(name){
this.name=name
}
//创建一个构造函数的实例
var person1=new person;c.通过Object.creat()方式创建
但是本质上3种方法都是通过new的方式创建的。
其中通过Object.creat(o)创建出来的对象他的隐式原型指向o。
通过对象字面量的方式创建的对象他的隐式原型指向Object.prototype。
构造函数function person本质上是由Function构造函数创建的,它是Function的一个实例。原型对象本质上是由Object构造函数创建的。内置函数Array Number等也是有Function构造函数创建的。
因此也就不难理解下面几个例子:
//通过new的方式
person1.__proto__===person.prototype //true
person.prototype.__proto__===Object.prototype //true
Object.__proto__===Function.prototype //true
//内置函数
Array.__proto__===Function.prototype //true
Array.prototype.__proto__===Object.prototype //true- javascript创建对象的几种方式
1.工厂模式2.构造函数模式
与工厂模式相比:
1、没有显式的创建对象
2、直接将属性和方法赋给了this对象
3、没有return语句
要创建person的实例,必须使用new操作符,以这种方式调用构造函数实际上会经历4个步骤:
1、创建一个新对象
2、将构造函数的作用域赋给新对象
3、执行构造函数中的代码
4、返回新对象
创建自定义的构造函数可以将它的实例标识为一种特定的类型。
构造函数的缺点:
每个方法都有在每个实例上重新创建一遍。person1和person2都有一个sayName()的方法,但两个方法不是同一个Function实例。不同实例上的同名函数是不相等的。
创建两个完成同样任务的Function实例没有必要,而且还有this对象在,不需要在执行代码前就把函数绑定在特定对象上,可以像下面这样。
把sayName属性设置成全局的sayName函数,这样,由于sayName包含的是一个指向函数的指针,因此person1和person2对象就共享了同一个函数。
但是,如果对象需要定义很多方法,那么就要定义很多全局函数,自定义的引用类型也没有封装可言了。为了解决上述问题,引入原型模式。
3.原型模式
理解原型对象 (关于原型上文有讲)
我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。prototype是通过调用构造函数而创建的那个对象实例的对象原型,使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
首先,解析器会问实例person1是否有name属性,如果有,就返回。 如果没有,就继续去person1的原型中搜索name属性,如果有就返回。 如果没有,再继续向person1的原型的原型中搜索。
isPrototypeOf()确定实例和原型对象之间的关联
console.log(Person.prototype.isPrototypeOf(person1)); //true
Object.getPrototypeOf()返回的是[[prototype]]的值
console.log(Object.getPrototypeOf(person1));
//Person {name: “Yvette”, age: 26, job: “engineer”} 返回的是Person的原型对象。
console.log(Object.getPrototypeOf(person1) === Person.prototype)//true
console.log(Object.getPrototypeOf(person1).name);//”Yvette”
hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中,只有给定属性存在于实例中,才会返回true。
console.log(person1.hasOwnProperty(“name”));//false
原型简写
这导致了person1.constructor不再指向Person,而是指向了Object。如果constructor很重要,则需要特意将其设为适当的值,如:
但是这种方式会导致constructor属性变成可枚举。
如果想设置为不可枚举的(默认不可枚举),可以使用
Object.defineProperty(Person.prototype, “constructor”, {
enumerable: false,
value: Person
}); 原型的动态性
由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来。
如果重写整个原型对象,情况就不一样了。调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。实例中的指针仅指向原型,而不指向构造函数。
person.prototype指向的是原本的原型对象,而不会指向新的原型对象。
原型对象的问题
原型模式最大问题是由其共享的本性所导致的。 对于包含引用类型值的属性来说,问题较为突出
本意只想修改person1的friends,但是却导致person2的friends属性值也改变了。因此我们很少单独使用原型模式。
4.组合使用构造模式和原型模式
创建自定义类型的最常用的方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性,这样每个实例都有自己的一份实例属性的副本,又同时共享着对方法的引用,最大限度的节省了内存。
除了以上几种方式以外,另外还有动态原型模式,寄生构造模式和稳妥构造模式,但是鉴于使用频率较低,不再赘述。
- 实现继承的多种方式和优缺点
1.prototype
//父类
function person(){
this.hair = 'black';
this.eye = 'black';
this.skin = 'yellow';
this.view = function(){
return this.hair + ',' + this.eye + ',' + this.skin;
}
}
//子类
function man(){
this.feature = ['beard','strong'];
}
man.prototype = new person();
var one = new man();
console.log(one.feature); //['beard','strong']
console.log(one.hair); //black
console.log(one.eye); //black
console.log(one.skin); //yellow
console.log(one.view()); //black,black,yellow
这种方式最为简单,只需要让子类的prototype属性值赋值为被继承的一个实例就行了,之后就可以直接使用被继承类的方法了。
2.构造函数(call)
let Super = function(name) {
this.name = name;
this.getName = () => {
return this.name;
}
}
let Sub = function(sex,name) {
Super.call(this,name); // 调用父类方法为子类实例添加属性
this.sex = sex;
}
let sub1 = new Sub('male','eric'),
sub2 = new Sub('female','eric');
sub1.name = 'ada';
console.log(sub2.name); // eric,实例的属性没有相互影响
console.log(sub1.getName === sub2.getName); // false,可见方法没有复用优点:子类的每个实例都有自己的属性(name),不会相互影响。
缺点:但是继承父类方法的时候就不需要这种特性,没有实现父类方法的复用。
3.组合式(call+prototype)
let Super = function(name) {
this.name = name;
}
Super.prototype = {
constructor: Super, // 保持构造函数和原型对象的完整性
getName() {
return this.name;
}
}
let Sub = function(sex) {
Super.call(this,'eric'); //继承父类属性
this.sex = sex;
}
Sub.prototype = new Super('eric'); //继承父类方法
Sub.prototype.constructor = Sub;
let sub1 = new Sub('male'),
sub2 = new Sub('female');
// 可以按上述两种方法验证,复用了父类的方法,实例没有复用,达到目的优点:继承了上述两种方式的优点,摒弃了缺点,复用了方法,子类又有各自的属性。
缺点:因为父类构造函数被执行了两次,子类的原型对象(Sub.prototype)中也有一份父类的实例属性,而且这些属性会被子类实例(sub1,sub2)的属性覆盖掉,也存在内存浪费。
- 匿名函数
在Javascript定义一个函数一般有如下三种方式:
函数关键字(function)语句:
function fnMethodName(x){alert(x);} 函数字面量(Function Literals):
var fnMethodName = function(x){alert(x);} Function()构造函数:
var fnMethodName = new Function(‘x’,’alert(x);’) 上面三种方法定义了同一个方法函数fnMethodName,
第1种就是最常用的方法,后两种都是把一个函数复制给变量fnMethodName,而这个函数是没有名字的,即匿名函数。匿名函数可以简单的理解为没有名字的函数,匿名自执行函数可以简单理解为可以自己执行的匿名函数 。
匿名函数的好处:匿名函数可以有效的保证在页面上写入Javascript,而不会造成全局变量的污染。 这在给一个不是很熟悉的页面增加Javascript时非常有效,也很优美。
匿名函数的常见场景:
<input type="button" value="点击" id="btn"> <script type="text/javascript">
//匿名函数的第一种情形
var btn=document.querySelector("#btn");
btn.onclick=function(){ // alert("aaaaa"); }
//匿名函数的第二种情形
setInterval(function(){ // alert("bbbbb"); }, 1000);
//匿名函数的第三种情形
var fun=function(){ alert("ccccc"); } // fun();
//匿名函数的第四种情形
var obj={ name:"dddd", say:function(){ alert(this.name); } }
obj.say();
</script> 匿名自执行函数,匿名自执行函数首先是一个匿名函数,但是这个函数是可以自己自动执行的,不需要借助其他的元素。使用匿名自执行函数将某些代码包裹起来可以实现块级作用域的效果,减少全局变量的数量,在匿名自执行函数执行结束后变量就会被内存释放掉,从而也会节省了内存。
<input type="button" value="点击" id="btn">
lt;script type="text/javascript"> //1,匿名函数的第一种实现方式
(function(data){ // alert(data); })("eee"); //2.匿名自执行函数的第二种实现方式
(function(){ // alert("fff"); }()); //3.匿名自执行函数的第三种实现方式
!function(data){ // alert(data); }("hhh"); //4.匿名自执行函数的第四种实现方式
var fun=function(data){ alert(data); }("iii");