/*js中的原型是是很重要的一个知识点,想要学好js就必须熟练掌握原型。

原型是js的一个属性:
但是他有一点特殊性,可以用来继承属性。*/
function A(name){
  this.name = name;
};
A.prototype.sayName = function(){
  console.log(this.name);
  return this.name;
}
//当我们实例化A时
var a = new A('jack');
a.sayName();

//此时打印出‘jack’;
//因此a的构造函数是A;
//挂载在A的原型属性上的sayName方法可以在A的实例a中调用。
//此时a有一个属性name值为‘jack’;
//一个方法getName;
//因此可以将实力化出来的a看成这种结构的一个对象;

obj = {
    name:'jack',
    getName:function(){
    console.log(this.name);
    return this.name;
  }
};

//当我们再申明一个构造函数B;
function B(name){

};
//我们将B的原型属性指向A的一个实例;
B.prototype = new A();//此时是没有传参(name属性);
/*因为new A()和a都是A实例化出来的,所以他拥有着和a同样的结构
那么就可以看成*/
B.prototype = {
  name:null,//因为实例化时没有传参
  getName:function(){
    console.log(this.name);
    return this.name;
  }
};
//即:
//B.prototype.name = null;
//B.prototype.getName = funtion(){
//  console.log(this.name);
//  return this.name;
//}
//实力化一个b
var b = new B('mary');
b.getName();//‘mary’
//因此B继承了A的属性;所以原型属性可以用来继承;

//关于constructor指向错误:
//构造函数Dog
function Dog(name){
  this.name = name;
}
Dog.prototype.say = function(){
  return this.name;
}
var dog = new Dog('哈士奇');
dog.constructor === Dog;//true
Dog.prototype.constructor === Dog;//true
dog.constructor.prototype.constructor === Dog;//true
//如果我们直接覆盖peototype属性,constructor的指向就会发生错误
function Dog(name){
  this.name = name;
}
Dog.prototype = {
  say:function(){
    return this.name
  }
};
var dog = new Dog('哈士奇');
dog.constructor === Dog;//false
Dog.prototype.constructor === Dog;//false
dog.constructor.prototype.constructor === Dog;//false;
//这是为什么呢?我们可以将上面代码理解成这样:
Dog.prototype = new Object({
  say:function(){
    return this.name;
  }
});
//这样就会出现
Dog.peototype.constructor = Object;
//当我们实例化Dog
var dog = new Dog('金毛');
dog.constructor === Object;//true dog的构造函数就变成了Object
dog.constructor.prototype.constructor === Object;//true
//解决这种问题非常简单,我们只要将Dog原型的构造函数重新指向Dog即可:
Dog.prototype.constructor = Dog;
var dog = new Dog('金毛');
dog.constructor === Dog;//true;
Dog.prototype.constructor === Dog;//true
dog.constructor.prototype.constructor === Dog;//true
//我们可以看jquery的源码也是这么解决的

/*更优雅的继承:
使用以上方法可以基本满足我们开发中关于继承的需求,
但是为了提升开发效率以及更容去理解,我们应该使用更优雅的方式,
网上以及相关的书籍中可以找到丰富多样的继承方法,但是都是万变不离其宗,
其中最多的就是使用原型继承(prototype);
先来看Crockford大神的写法吧:
首先为Function扩展一个method方法(用来为对象添加方法):*/
Function.prototype.method = function(name,func){
  this.prototype[name] = func;
  return this;
}
//inherits函数
Function.method('inherits',function(parent){
//parent可以理解为父类
  var d = {};
  var p = (this.prototype = new parent());//原型引用
  this.method('uber',function uber(name){
    if(!(name in d)){
      d[name] = 0;
    }
    var f,r,t = d[name],v = parent.prototype;
    if(t){
      while(t){
        v = v.construct.prototype;
        t-=1;
      }
    }else{
      f = p[name];
      if(f==this[name]){
        f = v[name];
      }
    }
    d[name]+=1;
r = f.apply(this,Array.prototype.slice.apply(arguments,[1]));
    d[name]-=1;
    return r;
  })
  return this;
});
//写的比较复杂,很多地方可以省略,作者可能考虑的情况比较多吧。
//单纯的写个继承单个方法更容易理解一些,比如下面这样,直接为Function扩展一个inherits方法用来继承;
Function.prototype.inherits = function(parent) {
  this.prototype = new parent();//继承父类
  this.prototype.constructor = this;//constructor重新指向
  this.prototype.uber = function(name) {
//为什么要用uber呢因为不想自己再想名字了
//比如《javascript面向对象编程指南》就是这种写法
    var f = parent.prototype[name];//定义f接收父类熟悉
//以下是为了可以传参继承父类方法
//arguments是参数(类数组,类似于domList需要使用Array的方法),call和apply可以看js文档用法
    return f.apply(this, Array.prototype.slice.call(arguments, 1));
  };
}
//看具体的使用方法:
function Dog(name){
  this.name = name;
}
Dog.prototype.say = function(xin){
  return xin+this.name;
}
function Jinba(name,prefix){
  this.name = name;
  this.prefix=prefix;
}
Jinba.inherits(Dog);
Jinba.prototype.say = function(){
  return this.uber('say','Jinba name ')//传参继承父类say方法
}
var smallJinba = new Jinba('smallDog','xiaojinba');
console.log(smallJinba.say());//Jinba name smallDog
console.log(smallJinba.constructor === Jinba)//true

//再来看看囧Resig的写法
var Animal = Class.extend({
  init:function(name){
    this.name = name;
  },
  getName:function(){
    return this.name;
  }
})
//Cat继承Animal
var Cat = Animal.extend({
  init:function(name,id){
    this._super(name);//继承父类同名属性
    this.id = id;
  },
  getId:function(){
    return this.id;
  },
  getName(){
    return 'cat name :'+this._super();//继承父类里的同名方法
  }
})
var smallCat = new Cat('mimi','10086');
smallCat.getName();//cat name:mimi
//看过jquery源码的兄台一定感觉上面的代码是曾相识;
//看看是怎么实现的吧:
(function(){
//首先一个自执行函数避免全局变量污染
  var initializing = false,fnTest = /xyz/.test(function(){xyz;})?/\b_super\b/:/.*/;  //一个正则为了做类型转换(判断是否为function)
  this.Class = function(){};//构造函数,这个this指向window 用于全局访问 因为是jquery基本是运行于浏览器
  Class.extend = function(prop){//给Class加一个extend属性
    var _super = this.prototype;//_super指向当前对象原型
    initializing = true;//开始初始化
    var prototype = new this();
    initializing = false
    for(var name in prop ){//循环对象属性列表
      prototype[name] = typeof prop[name]=="function"&&typeof _super[name]=="function"&&fnTest.test(prop[name])?
        (function(name,fn){
          return function(){
            var tmp = this._super;
            this._super = _super[name];
            var ret = fn.apply(this,arguments);
            this._super = tmp;
            return ret;
          }
        })(name,prop[name]):
        prop[name]
    }
    function Class(){
      if(!initializing&&this.init){
        this.init.apply(this,arguments);
      }
      Class.prototype = prototype;
      Class.constructor = Class;
      Class.extned = arguments.callee;//注意arguments.callee 已经从es5的严格模式种删除了
      return Class;
    }
  }
})()

/*下面在介绍几个基本继承的方法我们可以结合起来按照需求写出一套自己的继承方案*/

//一:原型链法(传统的继承方法,也是js文档中提到的最基本的写法)
Child.prototype = new Parent();
//这样子类就继承了父类的属性和方法;
//注意点:继承完父类后别忘了重新定义子类的constructor指向 Child.prototype.constructor = Child;
//使用技巧:将公用的方法属性写道原型(prototype属性)链中,不可重用的设置为对象自身属性(this.)

//二:仅从原型继承
Child.prototype = Parent.prototype
//容易理解一目了然,不存在原型链,所有对象共享一个原型对象,所以执行效率高
//缺点:因为只是对父类原型的一个引用,所以对子类的修改会影响的父类以及其他对这个父类原型引用的子类

//三:临时构造器
function extend(Child,Parent){
  var F = function(){};
  F.prototype = Prent.prototype;
  Child.prototype = new F;
  Child.prototype.constructor = Child;
  Child.uber = Parent.prototype;
}
//他只继承父类的原先属性(prototype上的属性),不继承父类自身熟悉(Parent函数内挂载到this上的);我们还可以通过uber属性访问父类

//深拷贝(防止修改子类影响到父类)
function deepCopy(p,c){
  c = c ||{};
  for(var i in p){//遍历出父类中所有属性
    if(typeof p[i]==='object'){
      c[i] = Array.isArray(p[i])?[]:{};//对数组和对象字面量另作深拷贝处理
      deepCopy(p[i],c[i])
    }else{
      c[i] = p[i];
    }
  }
  return c;
}
//先这样吧睡觉