对象的继承:通过原型对象继承;基于class继承。
// 这里只介绍通过原型对象继承
原型对象的概述
构造函数的缺点
function Person(name, age) { // 所有实例对象都会生成name age 属性 和say方法
this.name = name;
this.age = age;
this.say = function() {
console.log("Hello world!");
};
}
let per1 = new Person("中小余", 18);
let per2 = new Person("中小鱼", 17);
per1.say === per2.say; // false 同一个构造函数的多个实例之间无法共享属性,两个实例会生成两个同样的say方法,浪费系统资源,解决这个问题的方法就是“原型对象prototype”
prototype属性的作用
JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象
- 每一个函数都有一个prototype属性,指向一个对象
// 对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log("Hello world!");
};
let per1 = new Person("中小余", 18);
let per2 = new Person("中小鱼", 17);
per1.say === per2.say; //true
per1.say(); //'Hello world!'
per2.say(); //'Hello world!'
- 原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
Person.prototype.say = function() {
console.log("我是一只酸菜鱼,又酸又菜又多余!");
};
per1.say(); // '我是一只酸菜鱼,又酸又菜又多余!'
per2.say(); //'我是一只酸菜鱼,又酸又菜又多余!'
- 如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。如果实例对象上没有某个属性和方法的时候,它会到原型对象上去寻找该属性或方法。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log("Hello world!");
};
let per1 = new Person("中小余", 18);
let per2 = new Person("中小鱼", 17);
per1.say = function() {
console.log("我是一只酸菜鱼,又酸又菜又多余");
};
per1.say(); // '我是一只酸菜鱼,又酸又菜又多余' 他自身有say方法
per2.say(); // 'Hello world!' 他自身没有say方法 所以会去原型上找
原型链
- 所有的对象都有自己的原型对象
- 一方面任何一个对象,都可以充当其它对象的原型
- 另一方面由于原型对象也是对象,所以它也有自己的原型,因此会形成一个“原型链”(对象到原型,再到原型的原型….)
- 一层层往上追溯,所有对象的原型最终都可以追溯到
Object.prototype
,即Object
构造函数的prototype
属性。也就是说,所有对象都继承了Object.prototype
的属性,这就是所有对象都有valueOf
和toString
方法的原因,因为这是从Object.prototype
继承的。 Object.prototype
也有自己的原型,它的原型是null
,null
没有任何的属性和方法,也没有自己的原型,因此原型链的尽头就是null
。- Object.getPrototypeOf(对象名):返回参数对象的原型
- 一层层往上追溯,所有对象的原型最终都可以追溯到
constructor属性
prototype
对象有一个constructor
属性,默认指向prototype
对象所在的构造函数。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.constructor === Person;// true
constructor
属性定义在prototype
对象上面,意味着可以被所有实例对象继承
function Person(name, age) {
this.name = name;
this.age = age;
}
let per1 = new Person("中小鱼", 18);
per1.constructor === Person; // true
per1.constructor === Person.prototype.constructor; //true
per1.hasOwnProperty("constructor");//false
constructor
属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
function Person(name, age) {
this.name = name;
this.age = age;
}
function Man(name, age) {
this.name = name;
this.age = age;
}
let per1 = new Person("中小鱼", 18);
per1.constructor === Person; // true
per1.constructor === Man; //false
有了constructor
属性,就可以从一个实例对象新建另一个实例
function Person(name, age) {
this.name = name;
this.age = age;
}
let per1 = new Person("中小鱼", 18);
let per2 = new per1.constructor();
per2.constructor === Person; // true
per2 instanceof Person; //true
constructor
属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor
属性,防止引用的时候出错。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.constructor === Person; // true
Person.prototype = {
say: function() {
console.log("hello world!");
}
};
Person.prototype.constructor === Person;// false 修改了原型对象但是没有改constructor
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.constructor === Person; // true
Person.prototype = {
constructor: Person,// 修改constructor的指向
say: function() {
console.log("hello world!");
}
};
Person.prototype.constructor === Person; //true
/
// 另一种写法
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.constructor === Person;//true
Person.prototype.say = function() {// 另一种写法:只在原型对象上添加方法
console.log("hello world!");
};
Person.prototype.constructor === Person; //true
instanceof 运算符
- 返回一个布尔值 ,表示对象是否为某个构造函数的实例
function Person(name, age) {
this.name = name;
this.age = age;
}
let ins = new Person();
ins instanceof Person; // true
- 它会检查右边构造函数的原型对象(prototype)是否在左边对象的原型链上,因此还可以这么写
Person.prototype.isPrototypeOf(ins); // true
// isPrototypeOf()方法是 JavaScript 提供的原生方法,用于检查某个对象是否为另一个对象的原型
- 由于instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数返回true
let ins = new Person();
ins instanceof Person; // true
ins instanceof Object; // true
- 由于任意对象都是Object的实例(除了null),除了null其他对象的instanceOf Object的运算结果都是true,
所以instanceof运算等你可以判断一个值是否为非null的对象
ins instanceof Object;// true
null instanceof Object; // false
- instanceof 判断失真的情况
let obj = Object.create(null);
typeof obj; //'object'
obj instanceof Object; //false
//instanceof的原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上。
// 但左边对象的原型链上,只有null对象。这时,instanceof判断会失真。
- instanceof用来判断值的类型
let arr = [1, 2];
let obj = {};
let str = "string";
arr instanceof Array; // true
obj instanceof Object; // true
str instanceof String; //false instanceof运算符只能用于对象,不适用原始类型的值
undefined instanceof Object; // false
null instanceof Object; // false undefined和null,instanceof运算符总是返回false
- 可以解决调用构造函数时,忘记加new命令的问题
function Fubar (foo, bar) {
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
} else {
return new Fubar(foo, bar);
}
}
构造函数的继承
让一个构造函数继承另一个构造函数,分成两步实现:
- 1.在子类的构造函数中,调用父类的构造函数
- 2.让子类的原型指赂父类的原型,这样子类就可以继承父类原型
function Parent() {
this.x = 0;
this.y = 0;
}
Parent.prototype.say = function(x, y) {
this.x += x;
this.y += y;
console.log("Parent.say");
};
// 第一步:子类继承父类的实例
function Sub() {
Parent.call(this); //在子类的构造函数中,调用父类的构造函数
}
// 另一种写法
function Sub() {
this.legacy = Parent;
this.legacy();
}
// 第二步:子类继承父类的原型
Sub.prototype = Object.create(Parent.prototype);
/* sub.prototype是子类的原型,要将它赋值为Object.create(Parent.prototype);
而不是直接赋值为 Parent.prototype,因为以这种方式赋值对sub.prototype做操作的话也会修改父的的原型
*/
Sub.prototype.constructor = Sub; // 修改constructor属性的指向
let ins = new Sub();
ins instanceof Sub; // true
ins instanceof Parent; //true
ins.say(); //'Parent.say'
多重继承
JS中不允许一个对象同时继承多个对象,但是可以通过Mixin(混入)模式实现。
function Fun1() {
this.name = "中小余";
}
function Fun2() {
this.age = 18;
}
function Say() {
Fun1.call(this);
Fun2.call(this);
}
//继承fun1
Say.prototype = Object.create(Fun1.prototype);
//继承链上加入fun2
Object.assign(Say.prototype, Fun2.prototype);
//指定构造函数
Say.prototype.constructor = Say;
let talkAbout = new Say();
console.log(talkAbout.name + "就是" + talkAbout.age + "岁"); //'中小余就是18岁'
模块
基本的实现方法
- 模块是实现特定功能的一组属性和方法的封装
let comp = new Object(
name:'中小余',
age:18,
say:function(){
//...
},
do:function(){
// ...
}
);
comp.say();//调用对象的方法
// 这种方法会暴露所有模块成员,内部状态还可以被外部改写
还可以利用构造函数,封装私有变量
function Person() {
this.buffer = [];
}
Person.prototype = {
contructor: Person,
say: function(val) {
this.buffer.push(val);
},
toString: function() {
return this.buffer.join("");
}
};
let yu = new Person();
yu.say(1);
yu.buffer;//[1]
yu.toString();// '1'
yu.buffer = [3, 4, 5, 6, 7]; // 外部能改变私有变量
yu.say(1);
yu.toString();// '345671'
封装私有变量:立即执行函数
解决暴露私有成员的问题
let person = (function(){
let str0 = '小余要坚强鸭';
let str1 = '小余好南';
let fun1 = function(){
console.log(str1)
};
let fun2 = function(){
console.log(str0);
};
return function(){
this.fun1 = fun1;
this.fun2 = fun2
};
})()
let yu = new person();
yu.fun2(); // '小余要坚强鸭'
yu.str0 = '小余坚强';
yu.fun2(); // '小余要坚强鸭' str0没有变改变
模块的放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”(augmentation)。
var module1 = (function (mod) {
//...
return mod;
})(window.module1 || {});
输入全局变量
为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
(function($, window, document) {
function go(num) {
}
function handleEvents() {
}
function initialize() {
}
function dieCarouselDie() {
}
//attach to the global scope
window.finalCarousel = {
init : initialize,
destroy : dieCarouselDie
}
})( jQuery, window, document );
// finalCarousel对象输出到全局,对外暴露init和destroy接口,内部方法go、handleEvents、initialize、dieCarouselDie都是外部无法调用的