对象的创建:
一、单独声明定义一个对象
// 创建一个对象的两种方法;
// 方法1:new Object() 一个空白的,Object没有任何功能、属性的对象;不能在Array数值、Date日期、RegExp正则等对象上定义,否则可能会改变原有对象已定义的方法/属性;
let obj1 = new Object(); // 一个空的对象创建;
obj1.name = 'xiaoming';
obj1.age = 30;
obj1.say = function(){
console.log('我的名字叫:' + obj1.name + ',我的年龄是' + obj1.age); // 此处obj1 可以改成this,这样其他对象也能调用此方法;
}
obj1.say(); // 我的名字叫:xiaoming,我的年龄是30;
// 方法2:字面量的方式;
let obj2 = {
name:'xiaogou', // 对象整理赋给一个变量,则此处是',';
age:40,
say:function(){
console.log('我的名字叫:' + obj2.name + ',我的年龄是' + obj2.age); // 此处obj2 也可以改成this,这样其他对象也能调用此方法;
}
};
obj2.say(); // 我的名字叫:xiaogou,我的年龄是40
缺点:需要(利用一个接口)一个个的创建对象,每创建一个新的对象,则需要重复写许多相同的代码;可以改进一下;用函数将具体实现相同功能的代码包裹起来;
工厂模式
// 创建工厂模式;
function createPeople(name,age){ // 函数传递利用参数;
//原料;
let o = new Object(); //原料o,一个空的对象;
//加工;
o.name = name; // 给原料安装个螺丝,打个蜡.....;
o.age = age;
o.say = function(){
console.log('我的名字叫:' + o.name + ',我的年龄是' + o.age); // 同样,这里的o也可以改成this;
}
//出厂;
return o;
}
let people1 = createPeople('xiaoming',30); // 新创建返回的对象O 赋给 people1;
people1.say(); // 我的名字叫:xiaoming,我的年龄是30;
let people2 = createPeople('xiaogou',40);
people2.say(); // 我的名字叫:xiaogou,我的年龄是40;
console.log(people1 instanceof Object); // true;
console.log(people1 instanceof createPeople); // false;
缺点:1、创建的对象都是Object类型,没有办法知道其确切类型;对象中所有的函数都需要重新的定义,浪费内存;
构造函数模式;
// 创建构造函数模式,首字母大写;
function People(name,age){ // 函数传递利用参数;
//let this = new Object(); 默认有定义绑定,系统替做;
this.name = name;
this.age = age;
this.say = function(){
console.log('我的名字叫:' + this.name + ',我的年龄是' + this.age);
}
/***this.say = new Function("console.log('我的名字叫:' + this.name + ',我的年龄是' + this.age)")**/ // 创建一个新的函数对象实例;
//return this; 返回,系统替做;
}
let people1 = new People('xiaoming',30);
people1.say(); // 我的名字叫:xiaoming,我的年龄是30;
let people2 = new People('xiaogou',40);
people2.say(); // 我的名字叫:xiaogou,我的年龄是40;
console.log(people1 instanceof Object); // true;
console.log(people1 instanceof People); // true; //可以知道peopel的特定类型(属于People,而不是数组Array..日期Data..)
缺点:虽然知道了实例对象的确切类型,但是还是存在着每创建一个对象实例,所有的相同的属性、函数要重复定义,浪费内存;
原型模式:
// 创建原型模式;
function People(name,age){ // 函数传递利用参数;
this.name = name;
this.age = age;
}
/*** 彻底改变原型:
People.prototype = {
say:function(){
console.log('我的名字叫:' + this.name + ',我的年龄是' + this.age);
}
// 由于重新定义了原型,那么constructor会改变,需要重新手工设置;既:
constrctor:People;
// 由于重新定义了原型,那么此新的原型会无法作用在其之前创建的实例上;
}
****/
// 原型中追加方法:
People.prototype.say = function(){
console.log('我的名字叫:' + this.name + ',我的年龄是' + this.age);
}
let people1 = new People('xiaoming',30);
people1.say(); // 我的名字叫:xiaoming,我的年龄是30; 先在people1自己的实例中找say()方法,如果自己的实例中没有say方法,则去其原型中找;如果都没有则为undefine;
let people2 = new People('xiaogou',40);
people2.say(); // 我的名字叫:xiaogou,我的年龄是40;
console.log(people1 instanceof Object); // true;
console.log(people1 instanceof People); // true; //可以知道peopel的特定类型(属于People,而不是数组Array..日期Data..)
缺点:打破了面向对象的封装特性,一个类所有的属性和方法都应该定义在类中,而此方法,共用的属性和相同方法的函数都定义在了类的外部;
动态原型模式:
// 创建动态原型模式;
function People(name,age){ // 函数传递利用参数;
this.name = name;
this.age = age;
if(typeof this.say != 'function'){ // 第一次创建实例对象的时候,则会执行if语句,创建People的原型对象;
People.prototype.say = function(){
console.log('我的名字叫:' + this.name + ',我的年龄是' + this.age);
}
}
}
let people1 = new People('xiaoming',30);
// 当创建第一个People类的实例对象people1时候,则会执行if语句块;此时,People原型中并无任何自定义的属性、方法;
// 因此typeof this.say则会返回undefined,而不是function,所以就会执行if中的语句,这些自定义的属性和方法则追加到原型对象中;
people1.say(); // 我的名字叫:xiaoming,我的年龄是30;
let people2 = new People('xiaogou',40);
//当第二次利于People类创建people2对象的时候,由于this.say在原型对象中(先从自身对象中找say方法,不存在的话再从原型对象中找say方法)已经存在,则不在执行if语句;
people2.say(); // 我的名字叫:xiaogou,我的年龄是40;
console.log(people1 instanceof Object); // true;
console.log(people1 instanceof People); // true; //可以知道peopel的特定类型(属于People,而不是数组Array..日期Data..)
缺点:比较完美,但有点过时了(相对于es6的class);
寄生构造函数模式
主要用来扩展原生的构造函数的。比如为原生的Array扩展一些方法,如果直接在Array.prototype原型对象上做扩展,则不仅可能会污染其他的数值,还可能将原型对象改乱、改错;
寄生:则意味着新的构造函数,是在Array构造函数的基础上修改扩展的;
// 创建寄生构造函数模式; 构造函数里和工厂模式几乎一样;
function Myarray(){
//创建一个数组(实例);
let array = new Array();
//添加值;
array.push.apply(array,arguments); // 此时arguments,则表示Myarray函数中传递的参数;第一个参数为arguments[0]...
//此处,利用apply而不用call,是因为call将把arguments[0],arguments[1]...数组当成一个值(数组值)来进行传参;
//同理,不直接用array.push(arguments),也是因为这样整个arguments[0],arguments[1]...将会当成一个值(数组值)追加到array后面;
//利用apply主要目的是,apply的参数是数组,既将参数放在数组中,则arguments数组中的元素,一个个的当成array.push()的参数,传入push中追加;
//添加方法;
array.toPipedString = function(){
return this.join('|');
}
//返回数组;
return array;
}
//调用方法1:不建议用,因为没有new,既不能确切知道其对象类型;(是Array吗?)
let color = Myarray('red','green','blue');
console.log(color.toPipedString()); //red|green|blue;
console.log(color instanceof Object); // true;
console.log(color instanceof Myarray); // false;
console.log(color instanceof Array); // true; color是一个数组Array,这个数组不仅有Array所有的方法和属性,还有特性的toPipedString方法;
//调用方法2:使用new,体现了对象的构建过程;
//既然利用new了,如果构造函数没有return,那么new在创建实例(A)的时候,系统会默认return创建的(A);如果构造函数中有return 对象(B)的时候, 则将B对象赋给变量(reurn B);
//由于构造函数中的代码,创建的是新的对象(Array实例),并给此对象添加了属性、方法,所以,新创建的对象实例的proto、constructor指向的是Array,而不是Myarray;
let color2 = Myarray('red','green','blue');
console.log(color2.toPipedString()); // red|green|blue;
缺点:没有办法知道实例对象的确切类型,只知道它的原生对象类型;
稳妥构造函数模式:
// 创建稳妥构造函数模式; 构造函数里和工厂模式大致一样;
function People(name,age){
//创建要返回的对象;
let o = new Object();
// o.name = name;
// o.age = age; // 注释掉,安全级别较高的属性,直接在object对象上,可以被外界随意修改访问;
// 添加方式,安全获取name、age等等;
o.sayName = function(){ //只能通过sayName()方法,访问那么的值;
alert(name);
}
o.sayAge = function(){ //只能通过sayAge()方法,访问age的值;
alert(age);
}
//返回对象;
return o;
}
// 不能通过new创建,只能当做普通函数调用;
let friend = People('xiaoming',30);
friend.sayAge(); // 30;
friend.sayName(); //xiaoming;
对象的继承:
// 原型链继承
function Animal(name){
this.name = name ||'Animal';
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
if(typeof this.eat != 'function'){
Animal.prototype.eat = function(food){
console.log(this.name + '正在吃:' + food);
}
}
}
function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'Cat';
let xiaomao = new Cat();
xiaomao.sleep(); // Cat正在睡觉!
缺点:
- 没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数;(因为传的参数都属于子类的原型上的)
- 父类的属性值如果是引用类型的时候,父类的实例(子类的原型)则会存在此属性值,如果有一个子类的实例修改此引用类型的属性值的话,那么其他的子类实例调用此值的时候,也将会受到影响(因为是修改的子类原型上的值);