创建对象的几种方式
1.通过Object构造函数或者对象字面量创建单个对象;
这种方式的明显缺点:使用同一个接口创建多个对象,会产生大量重复代码。为了解决这个问题就出现了工厂模式。
2.工厂模式
实现起来就是在一个函数内创建好对象,然后把对象返回。
代码如下:
function createPerson(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
alert(this.name);
};
return 0;
}
var person1=createPerson("Nicholas",29,"Software Engineer");
var person2=createPerson("Greg",27,"Doctor");
工厂模式虽然解决了创建多个相似对象的问题,但是却没有解决对象识别的问题,即怎样知道一个对象的类型。
构造函数模式
像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
};
}
var person1=new Person(...);
var person2=new Person(...);
与工厂模式相比,具有以下特点:
- 没有显式创建对象;
- 直接将属性和方法赋给了this对象;
- 没有return语句;
- 要创建新实例,必须使用new操作符;(否则属性和方法将会被添加到window对象)
- 可以使用instanceof操作符检测对象类型
构造函数的问题:
构造函数内部的方法会被重复创建,不同实例内的同名函数是不相等的。可通过将方法移到构造函数外部解决这一问题,但面临新问题:封装性不好。
原型模式
我们创建每个函数都有一个porototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以有特定类型的所有实例共享的属性和方法。
使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="...";
Person.prototype.sayName=function(){
...
};
var person1=new Person();
person1.sayName();//"Nicholas"
原型对象的问题:
他省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值,虽然这会在一定程度带来一定的不便,但不是最大的问题,最大的问题是由其共享的本性所决定的。
对于包含基本值的属性可以通过在实例上添加一个同名属性隐藏原型中的属性。然后,对于包含引用数据类型的值来说,会导致问题。
这些问题导致很少单独使用原型模式。
组合使用构造函数模式和原型模式
这是创建自定义类型的最常见的方式。
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。所以每个实例都会有自己的一份实例属性的副本,但同时共享着对方法的引用,最大限度的节省了内存。同时支持向构造函数传递参数。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["S","C"];
}
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
};
var person1=new Person(...);
动态原型模式
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
if(typeof this.sayName!="function"){
Person.prototype.sayName=function(){
alert(this.name);
};
}
}
Object.create()
ES5定义了一个名为Object.create()的方法,它创建一个新对象,其中第一个参数是这个对象的原型,第二个参数对对象的属性进行进一步描述。
判断一个对象是否存在一个属性?
1.使用in关键字;
该方法可以判断对象的自身属性和继承来的属性;
var o={x:1};
"x" in o; //true,自有属性存在
"y" in o; //false
"toString" in o; //true,是一个继承属性
2.使用对象的hasOwnProperty()方法。
该方法只能判断自有的属性是否存在,对继承属性无法判断;
var o={x:1};
o.hasOwnProperty("x"); //true,自有属性中有x
o.hasOwnProperty("y"); //false,自有属性中不存在y
o.hasOwnProperty("toString"); //false,这是一个继承属性,但不是自有属性
3.用undefined判断
自有属性和继承属性均可判断。
var o={x:1};
o.x!==undefined; //true
o.y!==undefined; //false
o.toString!==undefined //true
该方法存在一个问题,如果属性的值就是undefined的话,该方法不能返回想要的结果,如下。
var o={x:undefined};
o.x!==undefined; //false,属性存在,但值是undefined
o.y!==undefined; //false
o.toString!==undefined //true
4.在条件语句中直接判断
var o={};
if(o.x) o.x+=1; //如果x是undefine,null,false," ",0或NaN,它将保持不变
继承方式
类的声明
/*类的声明*/
function Animal(){
this.name = 'name';
}
/*es6中类的声明*/
class Animal2(){
constructor(){
this.name = 'name';
}
}
类的实例化
console.log(new Animal,new Animal2);//实例化类如果没有参数可以不写括号
js的继承方式
借助构造函数实现继承
/*借助构造函数实现继承*/
/*这个继承方式只能实现部分继承,只能继承构造函数中定义的变量,当时他的prototype上的不能继承*/
function Parent1(){
this.name = 'name';
}
Parent1.prototype.say = function(){};//父级构造函数的prototype上的属性不能被继承
function Child1(){
Parent1.call(this);//关键代码,call将父级构造函数的this指向子函数,从而使子函数继承
this.type = 'child1';
}
console.log(new Child1);
原理:这种方式实现继承的原理是将父级构造函数this指向子类构造函数的实例上去;
缺点:这个方法的缺点是只能实现部分继承,只能继承父级构造函数中的属性,而prototype上的方法没有被继承。
2.借助原型链实现继承
function Parent2(){
this.name = 'name';
this.play = [1,2,3];
}
function Child2(){
this.type = 'type';
}
Child2.prototype = new Parent2();//,每个构造函数都有一个prototype属性,子构造函数的prototype属性可以任意赋值
console.log(new Child2);
//console.log(new child2()._proto_ == Child2.prototype);new child2()._proto_指向一个原型对象,这个原型对象是现在这个原型对象就是Child2.prototype,现在这个Child2.prototype赋值为new Parent2()
var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play);//1.2.3.4
console.log(s2.play);//1.2.3.4
原理:将子类的prototype属性赋值为父类的一个实例,而父类的实例就有父类的属性。
缺点:虽然实例了两个不同的对象,但是他们之间没有隔离,因为这两个对象是引用的是同一个对象(父类的实例对象)。
3.组合方式
/*组合方式继承*/
function Parent3(){
this.name = 'name';
this.play = [1,2,3];
}
function Child3(){
Parent3.call(this);
this.type= 'type';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
最通用的方式,结合上面两种方式。
缺点:父级的构造函数会执行两次;
4.组合继承的优化1
/*组合继承的优化1*/
function Parent4(){
this.name = 'name';
this.play = [1,2,3];
}
function Child4(){
Parent4.call(this);
this.type= 'type';
}
Child4.prototype = Parent4.prototype;
var s5 = new Child4();
var s6 = new Child4();
s5.play.push(4);
console.log(s5 instanceof Child4);//true
console.log(s5 instanceof Parent4);//true
console.log(s5.constructor);//parent4
原理:将父级构造函数的prototype赋值给子类构造函数的prototype,那么父类子类的的所有属性都可以继承了。
优点:父类构造函数不需要执行两次了,因为Parent4.prototype只是一个引用类型对象不需要执行构造函数。
缺点:不能区分实例对象是哪个父级构造函数直接实例化。
s5.constructor是parent4的原因是:
使用了同一个原型对象,那么他的constructor是同一个。
Child4.prototype = Parent4.prototype;
这句代码说明Child4的prototype已经被赋值为Parent4.prototype,那么child4的constructor已经不是child4的构造函数了,而是Parent4的构造函数,即child4的constructor已经继承了parent4的constructor,所以s5的constructor是parent4。
5.继承组合优化2(完美写法)
/*组合继承的优化2*/
function Parent5(){
this.name = 'name';
this.play = [1,2,3];
}
function Child5(){
Parent5.call(this);
this.type= 'type';
}
Child5.prototype = Object.create(Parent5.prototype);//Object.create创建了中间对象,这个中间对象的原型对象是parent5的原型对象
//经过上面代码虽然隔离了父类和子类原型对象但是child5的原型对象的constructor依然还是父类的constr
Child5.prototype.constructor = Child5;//指定Child5.prototype.constructor为child5
var s7 = new Child5();
console.log(s7 instanceof Child5,s7 instanceof Parent5);//true false
console.log(s7.constructor);//child5
**原理:**Object.create创建了中间对象,这个中间对象的原型对象是parent5的原型对象,这样就可以做到父类和子类原型对象的隔离。