一. 封装
1. 构造函数模式
// 首先创建一个构造函数
function Cat (name, color) {
this.name = name;
this.color = color;
}
// 实例化
const cat1 = new Cat('名字', '颜色')
console.log(cat1.name, '???name') // 名字
console.log(cat1.color, '???color') // 颜色
// 可以用instanceof的方法来判断实例化的方法是否属于构造杉树(注意没有点)
console.log(cat1 instanceof Cat, '???') // true
但是构造函数会存在一个浪费内存的问题
// 给这个构造函数添加一个eat方法
function Cat (name, color) {
this.name = name;
this.color = color;
this.eat = function () {
console.log('我的名字')
}
}
// 每次实例化之后相当于创建了一个新对象 对象里面的属性都是重复的内容,会多占用内存(相当于每次实例化出来的对象里面的属性会再一次被实例化出来)
var cat1 = new Cat("一","黄色");
var cat2 = new Cat ("二","黄色");
console.log(cat1.color); // 黄色
console.log(cat2.color); // 黄色
cat1.eat(); // 我的名字
cat2.eat(); // 我的名字
console.log(cat2.color == cat1.color) // true
console.log(cat1.eat == cat2.eat) // false
2. prototype模式
js规定,每一个构造函数上都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承,所以可以把那些公共的方法或者属性直接定义在prototype上面。
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "类型";
Cat.prototype.eat = function(){console.log("我的名字")};
var cat1 = new Cat("一","黄色");
var cat2 = new Cat ("二","黄色");
console.log(cat1.type); // 黄色
console.log(cat2.type); // 黄色
console.log(cat1.eat === cat2.eat); // true
// 它们都指向的同一个内存地址
// js提供了一些方法 方便配合prototype
// 1. isPrototypeOf() 判断构造函数里面有没有这个实例 (注意:要用构造函数的prototype去调用这个方法)
console.log(Cat.prototype.isPrototypeOf(cat1)); //true
console.log(Cat.prototype.isPrototypeOf(cat2)); //true
// 2. hasOwnProperty() 判断自身有没有这个属性 有的话返回true 否则返回false (注意:挂载到prototype上面的属性不属于自身属性,所以会返回false)
console.log(cat1.hasOwnProperty("color")); // true
console.log(cat1.hasOwnProperty("type")); // false
// in运算符 判断实例化的对象有没有这个属性 无论他是自身的还是prototype的
console.log("color" in cat1); // true
console.log("type" in cat1); // true
二. 继承
function Animal(){
this.species = "动物";
}
1.构造函数绑定/对象冒充 (通过apply或者call改变Animal的指向)
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黄色");
console.log(cat1.species); // 动物
2.prototype继承(*比较难理解)
/**
1. 首先需要知道的是构造函数的prototype.construct默认指向它本身这个构造函数(例如:Cat.prototype.constructor === Cat)
2. 其次实例化出来的对象的constructor默认指向构造函数的prototype.constructor(例如: cat1.constructor === Cat.prototype.constructor)
3. 所以实例化出来的对象的constructor默认指向它的构造函数 (例如:cat1.constructor === Cat)
*/
console.log(Cat.prototype.constructor) // Cat()
// 正常情况下Cat.prototype.constructor默认应该指向Cat的这个构造函数是没问题的
Cat.prototype = new Animal();
// 但是,在这里Cat.prototype把Cat这个构造函数的prototype对象修改成了Animal这个构造函数的实例化。
console.log(Cat.prototype.constructor) // Animal()
console.log(cat1.constructor) // Animal()
// 所以现在的Cat.prototype.constructor和他的实例化的指向为Animal这个构造函数
// 但是这段代码是什么意思呢
Cat.prototype.constructor = Cat;
// 因为在上述的案例中 通过修改Cat.prototype 使Cat.prototype.constructor指向了Animal这个构造函数,
// 但是cat1这个实例化出来的对象明明是指向Cat这个构造函数的。
// 所以我们要把Cat.prototype.constructor手动让其重新指向Cat这个构造函数
/**
所以切记!!! 即如果替换了prototype对象,
那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。
**/
console.log(Cat.prototype.constructor ) // Cat
// 最后既可以继承到Animal的这个属性并实例化的指向也没有变
var cat1 = new Cat("大毛","黄色");
console.log(cat1.species); // 动物
console.log(cat1.constructor === Cat.prototype.constructor) // true
3.直接继承prototype
// 声明一个构造函数 在prototype添加一个动物的属性
function Animal(){ }
Animal.prototype.species = "动物";
// Cat 不变
function Cat(name,color){
this.name = name;
this.color = color;
}
// 因为空函数几乎不占内存
var F = function(){};
F.prototype = Animal.prototype; // 同理改变F.prototype 的指向 F.prototype.constructor === Animal
Cat.prototype = new F();
// 同理改变Cat.prototype 的指向 Cat.prototype.constructor === F
// 因为上面 F.prototype.constructor === Animal
// 所以 Cat.prototype.constructor === Animal
Cat.prototype.constructor = Cat; // 改变了Cat.prototype.constructor的指向之后要记得把它改回来
console.log(Animal.prototype.constructor); // Animal