创建对象
new Object()
创建一个对象,然后给这个对象新建属性和方法。
var box = new Object(); //创建一个Object对象
box.name = 'Lee'; //创建一个name属性并赋值
box.age = 100; //创建一个age属性并赋值
box.run = function () { //创建一个run()方法并返回值
console.log(this);
return this.name + this.age + '运行中...';
};
console.log(box.run()); //输出属性和方法的值
//上面的方法等价于下面的方法
var box = {
name: "",
age: 100,
run: function () {
this.name;
this.age;
return this.name + this.age + '运行中...';
}
};
box.run()
上面创建了一个对象,并且创建属性和方法,在run()方法里的this,就是代表box对象本身。这种是JavaScript创建对象最基本的方法,但有个缺点,想创建一个类似的对象,就会产生大量的代码,直接获取对象的引用,导致数据混淆和变化。
缺点一:直接获取对象的引用,导致数据混淆和变化
var box2 = box; //得到box的引用
box2.name = 'Jack'; //直接改变了name属性
alert(box2.run()); //用box.run()发现name也改变了
缺点二:想创建一个类似的对象,就会产生大量的代码
var box2 = new Object();
box2.name = 'Jack';
box2.age = 200;
box2.run = function () {
return this.name + this.age + '运行中...';
};
alert(box2.run()); //这样才避免和box混淆,从而保持独立
工厂模式
为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。
//集中实例化的函数
function createObject(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return this.name + this.age + '运行中...';
};
return obj;
}
var box1 = createObject('Lee', 100); //第一个实例
var box2 = createObject('Jack', 200); //第二个实例
alert(box1.run());
alert(box2.run()); //保持独立
存在的问题:
工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法搞清楚他们到底是哪个对象的实例。
alert(typeof box1); //Object
alert(box1 instanceof Object); //true
构造函数(重点 )
function Box(name, age) { //构造函数模式
this.name = name;
this.age = age;
this.run = function () {
return this.name + this.age + '运行中...';
};
}
var box1 = new Box('Lee', 100); //new Box()即可
var box2 = new Box('Jack', 200);
alert(box1.run());
alert(box1 instanceof Box); //很清晰的识别他从属于Box
console.log(box1);
console.log(box2);
使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题是,这里并没有new Object(),为什么可以实例化Box(),这个是哪里来的呢?
使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:
1.构造函数方法没有显示的创建对象(new Object());
2.直接将属性和方法赋值给this对象;
3.没有return语句。
构造函数的方法有一些规范:
1.函数名和实例化构造名相同且大写,(注解:非强制,但这么写有助于区分构造函数和普通函数);
2.通过构造函数创建对象,必须使用new运算符;
关于this的使用,this其实就是代表当前作用域对象的引用。如果在全局范围this就代表window对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。
var box = 2;
alert(this.box); //全局,代表window
构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用new运算符来调用,否则就是普通函数。
var box = new Box('Lee', 100); //构造模式调用
alert(box.run());
Box('Lee', 20); //普通模式调用,无效
var o = new Object();
Box.call(o, 'Jack', 200) //对象冒充调用
log(o);
log(o.run());
原型
prototype通过调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
function Box() {} //声明一个构造函数
Box.prototype.name = 'Lee'; //在原型里添加属性
Box.prototype.age = 100;
Box.prototype.run = function () { //在原型里添加方法
return this.name + this.age + '运行中...';
};
比较一下原型内的方法地址是否一致:
var box1 = new Box();
var box2 = new Box();
alert(box1.run == box2.run); //true,方法的引用地址保持一致
为了更进一步了解构造函数的声明方式和原型模式的声明方式,我们通过图示来了解一下:
构造函数方式:this.xxx
原型模式方式:prototype
注解:__proto__属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor。通过这两个属性,就可以访问到原型里的属性和方法了
**isPrototypeOf()?*判断对象是不是该构造函数的实例
判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。
alert(Box.prototype.isPrototypeOf(box)); //只要实例化对象,即都会指向ture
原型模式的执行流程:
1.先查找构造函数实例里的属性或方法,如果有,立刻返回;
2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
知识点:虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原型中的值。
var box1 = new Box();
alert(box1.name); //Lee,原型里的值
box1.name = 'Jack';
alert(box1.name); //Jack,就近原则,
var box2 = new Box();
alert(box2.name); //Lee,原型里的值,没有被box1修改
hasOwnProperty()方法
如何判断属性是在构造函数的实例里,还是在原型里?可以使用hasOwnProperty()函数来验证:
console.log(构造.hasOwnProperty("name"));
alert(实例.hasOwnProperty('name')); //实例里有返回true,否则返回false
构造函数实例属性和原型属性示意图
操作符in
in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
alert('name' in box); //true,存在实例中或原型中
原型对象字面量方式
为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使用字面量的方式:
function Box() {};
Box.prototype = { //使用字面量的方式
name : 'Lee',
age : 100,
run : function () {
return this.name + this.age + '运行中...';
}
};
处理原型对象属性constructor
使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区别,字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反。
var box = new Box();
alert(box instanceof Box);
alert(box instanceof Object);
alert(box.constructor == Box); //字面量方式,返回false,否则,true
alert(box.constructor == Object); //字面量方式,返回true,否则,false
如果想让字面量方式的constructor指向实例对象,那么可以这么做:
Box.prototype = {
constructor : Box, //直接强制指向即可
};
注解:字面量方式为什么constructor会指向Object?因为Box.prototype={};这种写法其实就是创建了一个新对象。而每创建一个函数,就会同时创建它prototype,这个对象也会自动获取constructor属性。所以,新对象的constructor重写了Box原来的constructor,因此会指向新对象,那个新对象没有指定构造函数,那么就默认为Object。
原型的重写
原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。
function Box() {};
Box.prototype = { //原型被重写了
constructor : Box,
name : 'Lee',
age : 100,
run : function () {
return this.name + this.age + '运行中...';
}
};
//切断之前的原型
Box.prototype = {
age : 200
};//调用不到所以不推荐
//推荐使用方式:
Box.prototype.age2=200;
var box = new Box(); //在这里声明
alert(box.run()); //box只是最初声明的原型
组合:构造函数,原型模式
原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
function Box() {};
Box.prototype = {
constructor : Box,
name : 'Lee',
age : 100,
family : ['父亲', '母亲', '妹妹'], //添加了一个数组属性
run : function () {
return this.name + this.age + this.family;
}
};
var box1 = new Box();
box1.family.push('哥哥'); //在实例中添加'哥哥'
alert(box1.run());
var box2 = new Box();
alert(box2.run()); //共享带来的麻烦,也有'哥哥'了
解决方案如下:
function Box() {
//建议:把所有的属性放在 实例对象中
//不共享的使用构造函数
this.name = 'Lee';
this.family = ['父亲', '母亲', '妹妹'];//this.family = new Array();
this.age = 100;
};
//建议:把所有的方法(行为)放在原型对象中
//共享的使用原型模式
Box.prototype = {
constructor: Box,
run: function () {
return this.name + this.age + '运行中...';
}
};
var box1 = new Box();
box1.age = 300;
box1.family.push("哥哥");
var box2 = new Box();
console.log(box1);
console.log(box2);
注解:这种混合模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法。
动态原型模式
原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在声明一个对象时,构造函数+原型部分让人感觉又很怪异,最好就是把构造函数和原型封装到一起。为了解决这个问题,我们可以使用动态原型模式。
function Box(name ,age) { //将所有信息封装到函数体内
this.name = name;
this.age = age;
if (typeof this.run != 'function') { //仅在第一次调用的初始化
Box.prototype.run = function () {
return this.name + this.age + '运行中...';
};
}
}
var box = new Box('Lee', 100);
alert(box.run());
寄生组合继承
function obj(o) {
function F() { }
F.prototype = o; //F.constructor ---> Box
return new F();
}
/*
* 让子类继承父类的原型中的所有方法
*/
function create(box, desk) {
var f = obj(box.prototype); //f.constructor--->Box
f.constructor = desk;//这个操作:让f.constructor---> Desk
desk.prototype = f;//让子类继承父类的原型中的所有方法,并且Desk.constructor-->Desk
}
function Box(name) {
this.name = name;
this.arr = ['哥哥', '妹妹', '父母'];
this.arr2 = ["cell1", "cell2"];
this.arr3 = ["cell1", "cell2"];
this.arr4 = ["cell1", "cell2"];
this.arr5 = ["cell1", "cell2"];
}
Box.prototype.run = function () {
return this.name;
}
/*
* 第一个思路:让子类中的实例对象拥有父类实例对象中的所有属性;
* 第二个思路:让子类继承父类的原型中的所有方法,但是constructor依然要指向子类的构造函数
*/
function Desk(name, age) {
//Box.call()方法,主要作用是,让Desk的this实例对象继承Box中的this实例对象中所有的属性
Box.call(this, name);//当做普通函数执行Box方法
this.age = age;//保持子类中的特点
}
create(Box, Desk);//通过这里实现继承父类中所有方法的操作,并且Desk.constructor-->Desk
//扩展:子类中的方法
//注意:扩展子类中方法的时候,一定要卸载 create继承的后面
Desk.prototype.run2 = function () {
console.log(this.arr2);
}
var desk = new Desk('Lee', 100);
var desk2 = new Desk('Jack', 200);
console.log(desk);
console.log(desk.run());
desk.run2();
// console.log(desk2);
// desk.arr.push('姐姐');
// alert(desk.arr);
// alert(desk.run());//只共享了方法
// var desk2 = new Desk('Jack', 200);
// alert(desk2.arr);//引用问题解决