继承发展史
-
传统模式 ----> 原型链
过多的继承了没用的属性 -
借用构造函数
不能继承借用函数的原型
每次构造函数都要多走一个函数 -
共享原型
不能随便改动自己的原型
Father.prototype.lastName = 'Deng';
function Father() {
}
function Son() {
}
Son.prototype = Father.prototype;
var son = new Son();
var father = new Father();
Father.prototype
Father Son
- 圣杯模式
function inherit(Target,Origin) {
function F() {};
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constuctor = Target;
Target.prototype.uber = Origin.prototype;
}
var inherit = (function () {
var F = function () {};
return function (Target,Origin) {
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constuctor = Target;
Target.prototype.uber = Origin.prototype;
}
}());
//两种都可,建议写下面的
Father.prototype.lastName = 'Deng';
function Father() {
}
function Son() {
}
inherit(Son,Father);
var son = new Son();
var father = new Father();
私有化变量
function Deng(name,wife) {
var prepareWife = 'xiaozhang';
this.name = name;
this.wife = wife;
this.divorce = function () {
this.wife = prepareWife;
}
this.changPrepareWife = function (target) {
prepareWife = target;
}
this.sayPrepareWife = function() {
console.log(prepareWife);
}
}
var deng = new Deng ('deng','xiaoliu');
六种继承方式
方式一、原型链继承
- 子类型的原型为父类型的一个实例对象。
//父类型
function Person(name, age) {
this.name = name,
this.age = age,
this.play = [1, 2, 3]
this.setName = function () { }
}
Person.prototype.setAge = function () { }
//子类型
function Student(price) {
this.price = price
this.setScore = function () { }
}
Student.prototype = new Person() // 子类型的原型为父类型的一个实例对象
var s1 = new Student(15000)
var s2 = new Student(14000)
console.log(s1,s2)
子类继承父类的属性和方法是将父类的私有属性和公有方法都作为自己的公有属性和方法
优点
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点
- 无法实现多继承 : 如果说父类的私有属性中有引用类型的属性,那它被子类继承的时候会作为公有属性,这样子类1操作这个属性的时候,就会影响到子类2。
- 要想为子类新增属性和方法,必须要在
Student.prototype = new Person()
之后执行,不能放到构造器中 - 来自原型对象的所有属性被所有实例共享
方式二: 借用构造函数继承
在子类型构造函数中通用call()调用父类型构造函数
function Person (name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.setName = function () {}
}
Person.prototype.setAge = function () {}
function Student (name,age,sex,grade) {
Person.call(this,name,age,sex);
//Person.apply(this,[name,age,sex]);
this.grade = grade;
}
var student = new Student('aaa',30,'male',6);
这种方式只是实现部分的继承,如果父类的原型还有方法和属性,子类是拿不到这些方法和属性的。
console.log(s1.setAge())
//Uncaught TypeError: s1.setAge is not a function
优点
- 解决了原型链继承中子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
缺点
- 只能继承父类的实例属性和方法,不能继承原型属性和方法
方式三: 原型链+借用构造函数的组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
function Person(name, age) {
this.name = name,
this.age = age,
this.setAge = function () { }
}
Person.prototype.setAge = function () {
console.log("111")
}
function Student(name, age, price) {
Person.call(this,name,age)
this.price = price
this.setScore = function () { }
}
Student.prototype = new Person()
Student.prototype.constructor = Student//组合继承也是需要修复构造函数指向的
Student.prototype.sayHello = function () { }
var s1 = new Student('Tom', 20, 15000)
var s2 = new Student('Jack', 22, 14000)
console.log(s1)
console.log(s1.constructor) //Student
console.log(p1.constructor) //Person
优点
- 可以继承实例属性/方法,也可以继承原型属性/方法
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点
- 调用了两次父类构造函数,生成了两份实例
方式四: 组合继承优化1
父类原型和子类原型指向同一对象,子类可以继承到父类的公有方法当做自己的公有方法,而且不会初始化两次实例方法/属性,避免的组合继承的缺点。
function Person(name, age) {
this.name = name,
this.age = age,
this.setAge = function () { }
}
Person.prototype.setAge = function () {
console.log("111")
}
function Student(name, age, price) {
Person.call(this, name, age)
this.price = price
this.setScore = function () { }
}
Student.prototype = Person.prototype
Student.prototype.sayHello = function () { }
var s1 = new Student('Tom', 20, 15000)
console.log(s1)
// 没办法辨别是对象是子类还是父类实例化
console.log(s1 instanceof Student, s1 instanceof Person)//true true
console.log(s1.constructor)//Person
优点
- 不会初始化两次实例方法/属性,避免的组合继承的缺点
缺点
- 没办法辨别是实例是子类还是父类创造的,子类和父类的构造函数指向是同一个。
方式五: 组合继承优化2
借助原型可以基于已有的对象来创建对象,var B = Object.create(A)以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
function Person(name, age) {
this.name = name,
this.age = age
}
Person.prototype.setAge = function () {
console.log("111")
}
function Student(name, age, price) {
Person.call(this, name, age)
this.price = price
this.setScore = function () {}
}
Student.prototype = Object.create(Person.prototype)//核心代码
Student.prototype.constructor = Student//核心代码
var s1 = new Student('Tom', 20, 15000)
console.log(s1 instanceof Student, s1 instanceof Person) // true true
console.log(s1.constructor) //Student
console.log(s1)
Student继承了所有的Person原型对象的属性和方法。目前来说,最完美的继承方法!
方式六:ES6中class 的继承
ES6中引入了class关键字,class可以通过extends关键字实现继承
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
// ES6 类继承
// 父级构造函数
class Phone{
// 构造方法
constructor(brand,price){
this.brand = brand;
this.price = price;
}
// 父类的成员属性
call(){
console.log("调用父类的方法")
}
}
// 子级构造函数
class SmartPhone extends Phone{
// 构造方法
constructor(brand,price,color,size){
super(brand,price);// super就是父类的constructor 相当于Phone.call(this,brand,price); // this 指向Smartphone
this.color = color;
this.size = size;
}
call(){
console.log("调用子类的方法")
}
}
// 实例化对象
const xiaomi = new SmartPhone('小米',2999,'白色','4.7inch');
console.log(xiaomi);
xiaomi.call(); // 调用子类的方法
// 子类对父类方法重写
// 重写:可以在子类里声明一个跟父类同名的方法
// call(){
// console.log('视频通话');
// }
// 子类不能直接调用父类同名方法
// super不能用,报错。在普通的成员方法中不能出现super调用父类同名方法的
// static 定义静态方法和属性
// ES5
function Phone(brand,price){
this.brand = brand;
this.price = price;
}
// 构造函数本身也是一个对象
Phone.name = '手机';
Phone.call = function(){
console.log('我可以打电话');
}
Phone.prototype.size = '5.5. sinch';
let nokia = new Phone();
console.log(nokia.name); // undefined 实例对象身上没有构造函数对象身上的属性
nokia.call(); // Uncaught TypeError: nokia.call is not a function
// 实例对象和函数对象的属性不通,实例 对象属性和 构造函数原型 对象属性相通
// 函数对象的属性属于函数对象,不属于实例对象,这样的属性称为静态成员,该属性属于类,不属于实例对象
console.log(nokia.size);
// ES6
class Phone{
// 静态属性
static name = '手机';
static call(){
console.log('我可以打电话');
}
}
// 实例化
let nokia = new Phone();
console.log(nokia.name); // undefined
console.log(Phone.name); // 手机
// static 标注的属性和方法,属于类,不属于实例对象
参考文章 :六种继承方式
命名空间
管理变量,防止污染全局,适用于模块化开发
以前
var org = {
department1 : {
jicheng : {
name : 'abc';
age : 123;
},
xuming : {
}
},
department2 : {
jicheng : {
name : 'abc';
age : 123;
},
xuming : {
}
}
}
var jicheng = org.department1.jicheng;
jicheng.name
防止命名空间
var name = 'bcd';
var init = ( function () {
var name = 'abc';
function callName() {
console.log(name);
}
return function() {
callName();
}
}())
init();//入口,初始化
如何实现链式调用模式(模仿jquery)
var deng = {
smoke : function () {
return this;
}
drink : function () {
return this;
}
perm : function () {
return this;
}
}
deng.smoke().drink().perm();
属性表示方法
[字符串形式的属性名]
obj.name ----> obj['name']
var deng = {
wife1 : {name : "xiaoliu"},
wife2 : {name : "xiaozhang"},
wife3 : {name : "xiaomeng"},
wife4 : {name : "xiaowang"},
sayWife : function (num) {
return this['wife' + num];
}
}
遍历:挨个了解每个人的信息
对象枚举
1. for in
- 每一圈循环将属性名传到prop里,循环圈数为属性个数
var obj = {
name : 'abc',
age : 123,
sex : 'male'
}
for(var prop in obj) {
console.log(prop + ' ' + typeof(prop));// name string
console.log(obj[prop]); // abc
}
2. hasOwnProperty
for in 会打印出原型
obj.hasOwnProperty(‘属性名’) ----> 判断属性是否属于对象的属性方法,返回Bollean值
for(var prop in obj) {
if(!obj.hasOwnProperty(prop)) {
console.log(obj[prop]);
}
}
会打出自己设置的属性方法,系统带的不会打印
3. in
'属性名’ in obj
返回布尔值,只能判断对象是否能访问到这个属性,原型上也能用
4. instanceof
A 对象是不是 B 构造函数构造出来的
看A 对象的原型链上 有没有 B 的原型
A instanceof B
判断变量是数组还是对象
a.constructor
a instanceof Array a instanceof Object
- toString
Object.prototype.toString.call([]); //Object.prototype.toString = function () { //识别 this //返回相应的结果 //}