JavaScript 继承原理

前置基础

  • get set

    //getter 和 setter 都是一个隐藏函数,会在获取属性的值或者设置属性的值得实惠调用
    //不定义就是默认操作,也就是平时 使用的obj.a = 5 就是把obj.a设置成5
    //自己定义也可以,如果一个属性,你给他定义了 getter或者setter或者两个都有,这个属性会被定义成
    //访问描述符(还有一个叫数据描述符的,这两个玩意之后帖子再说),这次就先简单认识下
    
    var obj = {
      get a(){
          return this._a_;
      }
      ,
      set a(val){
          this._a_ = val;
      }
    }
    
    
    
    obj.a = 5;
    console.log(obj.a);//5
    
    //好像不写get set也是一样的效果哈。。。
    var obj = {}
    obj.a = 5;
    console.log(obj.a);//5
    
    //改下代码
    
    var obj = {
      get a(){
          return this._a_;
      }
      ,
      set a(val){
          this._a_ = val*2;
      }
    }
    
    
    
    obj.a = 5;
    console.log(obj.a);//10
    //有变化了 再改下
    
    
    var obj = {
      get a(){
          return 2;
      }
    }
    
    obj.a = 5;
    console.log(obj.a);
    //没设置 set set默认操作 但是 get 直接返回2  所以。。。。。。就是2
    
  • for in 和属性描述表
var arr = [1,2,3];

//for in 可以遍历数组
for(let key in arr){
    console.log(arr[key])
}

var _obj = {
    "one":1,
    "two":2
}
//for in 也可以遍历object
showObj(_obj);

//其实for in 是会遍历 目标所有可枚举的值

//通过Object.getOwnPropertyDescriptor 来查看 arr[0] 的属性描述表
showObj(Object.getOwnPropertyDescriptor(arr,"0"));
// value: 1  数值 是 1
// writable: true  可写 代表可以修改该值
// configurable: true 可配置 代表可以修改该值得属性描述表
// enumerable: true 可枚举 for in能否遍历到就看这个值是否为 true

//修改 arr[0]的属性描述表 使得它不可枚举
Object.defineProperty(arr,"0",{
    
    enumerable:false
})

//for in 无法遍历到 arr[0]
for(let key in arr){
    console.log(arr[key])
}
//发现确实 enumerable: true
showObj(Object.getOwnPropertyDescriptor(arr,"0"));

//数据描述表其他用处就不多说了。列举一个
//要设置一个常量 可以使得它的 writable configurable均为假即可。
//话说。。设置不可配置让他权限降级是可以的,也就是说设置了不可配置之后 本来只读要变成读写是不行的,但是反过来可以


function showObj(obj){
    for(let key in obj){
        console.log(key + " : " + obj[key]);
    }
}

  • call apply bind
function fun(){
    this.x = 2;
    
    this.show = function(){
        console.log(this.x);
    }
    
    this.show2 = function(val,val2){
        console.log(val+val2+this.x);
    }
}

var num = {
    x:1
}

var x = 10;

var obj = new fun();
obj.show();//2
//不解释

obj.show.call(num);//1
//call 调用obj.show 并且改变其this 指向num 所以 console.log(this.x); this.x 是 num.x也就是1
obj.show.call();
obj.show.call(null);
obj.show.call(undefined);
//上面三个都是 10 不传或者传入 null 或者 undefined 都会指向window

obj.show2(1,2);//1+2+2 = 5

obj.show2.call(obj,3,2);//3+2+2 = 7
//call还可以传入参数
obj.show2.call(num,0,0);//0+0+1 = 1

obj.show2.apply(num,[0,0]);//apply 和call 的区别在于 传参方式不同
//还有一个bind 也能有类似效果
obj.show2.bind(num,1,2)();//1+2+1 = 4
//也是修改了show的this指向但是区别在于bind是创建了一个函数称为绑定函数
//调用该函数时会以创建它时传入 bind()方法的第一个参数作为 this,
//传入 bind方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序
//作为原函数的参数来调用原函数。
//也可以写成
var f = obj.show2.bind(num,1,2);//bind 创建了一个绑定函数
f();//调用绑定函数 执行原函数




//apply 数组传参可以解决变长参数的问题如下

function log(){
    console.log.apply(console, arguments);
}

log(1,2,3);

继承

  • 构造函数继承

`
首先来说,js和典型的oop语言如c++有一个很大的区别,js没有类的概念(ES6引入了class。。额。。class就是个语法糖)就好像js可以定义一个数组,每个元素都是长度相同的数组,以此来模拟二维数组一样,js对于类的继承也是如此。
`

    function A(val){

    this.val = val;

}

`
如果我想定义一个B继承自A,最核心的需求就是要能使用 A 的所有内容,比如这里只有一个变量val。
`

function B(val){

    this.fun = A;//this.fun 指向A的构造函数

    this.fun(val);//调用A的构造函数,实例化对象

    delete this.fun;//删除fun函数

    this.show = function(){

        console.log(this.val);

    }

}

`
如上我在B内部调用A的构造函数实例化了一个A,当然可以使用A的所有内容。为什么不能直接调用A(val),
`
`
原因是英文必须要A内 this.val = val; 和B 内console.log(this.val); 这两个this是同一个this。那么有没有什么简便的写法呢?答案是肯定的。只要在调用构造函数的时候吧this修改成和B一样的this即可。也就是call。
`

function B(val){

    A.call(this,val);

    this.show = function(){

        console.log(this.val);

    }

}

`
用同样的思想也可以实现多继承,但是由于js都没有类的概念,也就更没有没有虚基类这一说法,所以如果就想要用多继承,多继承的各种坑要自己填。。。
`

  • 原型继承
function Foo(){
    this.a = 0;
    Foo.prototype.b = 1;
}

var foo = new Foo();

//首先明白一件事
console.log(Foo.a);//undefiend
console.log(foo.a);//0
//Foo 只是一个你定义的函数。。。不能直接用。。。

//看如下代码
//getPrototypeOf 就是获取到对象的prototype
console.log(Object.getPrototypeOf(foo) === Foo.prototype);//true
console.log(Object.getPrototypeOf(foo) === Foo);//false
//先明白一点。由Foo创建的对象的prototype不是指向Foo而是Foo.prototype

console.log(foo.a);//0
console.log(Foo.prototype.a);//undefiend
//a 是存在于foo的或者说是Foo的 而不是 Foo.prototype的

console.log(foo.b); // 1
console.log(Foo.prototype.b);//1
console.log(foo.hasOwnProperty("b"));//false
console.log("b" in foo);//true
//以上四句代码
//在我的prototype上的属性 我和我的prototype都能用
//hasOwnProperty 只检测 我有没有
//in 会先检测我有没有 如果没有会继续检测我的prototype有没有。。。

//那么继承。。。。 是不是只要把你要继承的东西放在你的prototype里,然后我的prototype指向你的prototype就好了
//当然也可以我的prototype直接指向你。

//下面这句话的意思 目前简单来理解就是创建一个空的对象然后 obj.prototype指向Foo 为什么要写foo 看最开始 这也是是指向prototype的的一个优点吧
var obj = Object.create(foo);
console.log(Object.getPrototypeOf(obj) === foo);//true
//同上
var obj2 = Object.create(Foo.prototype);
console.log(Object.getPrototypeOf(obj2) === Foo.prototype);//true

//测试一下是否能继承到Foo的东西
console.log(obj2.a);//undefiend
console.log(obj2.b);//1

console.log(obj.a);//0
console.log(obj.b);//1
//没毛病



//首先 我们明白变量屏蔽 简单来说就是局部变量屏蔽全局变量
//这事就不废话了。。
//那么 如果 一个对象本身有 a 属性 他的prototype上也有会怎么样呢?
//用代码说话
function Foo(){
    this.a = 0;
    Foo.prototype.a = 1;
}

var foo = new Foo();

console.log(foo.a); //0
//由此可见 本身的属性会屏蔽 prototype上的属性

var Bar = {
    get a(){
        return "a";
    },
    set a(val){
        console.log("给a赋值了")
        this._a_ = val;
    },
    "b":"b",
    "c":"c"
}


var bar = Object.create(Bar);
console.log(bar.a,bar.b,bar.c)
//可以打印 a b c

console.log("a:"+bar.hasOwnProperty("a"))
console.log("b:"+bar.hasOwnProperty("b"))
console.log("c:"+bar.hasOwnProperty("c"))
//均为假 说明都在bar.prototype上

Object.defineProperty(bar,"c",{
    
    writable:false
})
//修改 c 不可写


//给bar的 a b c赋值 问 会有什么情况发生?
//在bar 上 创建对应变量 然后屏蔽他 prototype上的同名变量?
//还是直接修改他 prototype上的那个变量?
bar.a = 1;
//给a赋值了
bar.b = 2;
bar.c = 3;
console.log(bar.a,bar.b,bar.c);
// a  2  undefiend
//由此可见 三种不同的情况 结果不尽相同
//看看他们到底在bar上还是在prototype上
console.log("a:"+bar.hasOwnProperty("a"))//false
console.log("b:"+bar.hasOwnProperty("b"))//true
console.log("c:"+bar.hasOwnProperty("c"))//true
//也就是说 如果设置了 set 那就是直接调用prototype上的对应 set
//如果可写 就会创建一个同名变量 屏蔽
//如果只读 创建成功 但是无法赋值

//之前的帖子里我已经解释过 基本的继承方法
function Foo(){
    this.a = 0;
    Foo.prototype.b = {x:1}
}

var foo = new Foo();//写这句哈的原因还是 不写的话 Foo 没意义 自然也就不能Foo.prototype

function Bar(){
    this.c = {x:2}
}

//继承 Foo的 prototype上的内容
Bar.prototype = Object.create(Foo.prototype)


var bar = new Bar();
console.log(bar.b.x);//1
console.log(bar.c.x);//2
//可以 貌似除了不能访问a没问题。。但是。。。。
var bar2 = new Bar();
bar2.b.x=5;
console.log(bar.b.x);//5
console.log(bar.c.x);//2
//由于bar bar2 都是new Bar得到的然而 Bar.prototype 是 foo(注意不是Foo) 是我创建的一个变量的一个属性
//那我创建的这一个变量的属性肯定只有一个值,当我bar2修改了之后bar那访问当然也是修改之后的值
//要满足继承的要求 要分成公用的和非公用的两种,简单来说有一些东西确实不需要一人一份,比如函数,或者共享的变量
//比如其他语言的静态变量,但是也有不少属性需要bar2 的修改不影响到bar,而且既然Bar继承了Foo应该要能访问a
//所以 解决办法就是Foo把公用的写在prototype上,不公用的就和a这样直接写,然后Bar 先创建一个对象获取到所有
//类似于a这样的属性,然后在修改prototype为Foo.prototype

function Foo_(){
    this.a = 0;
    Foo_.prototype.b = {x:1}
}

function Bar_(){
    Foo_.call(this);//可以看我之前的帖子
    this.c = {x:2}
}

Bar_.prototype = Object.create(Foo_.prototype)

var bar_ = new Bar_();
var bar2_ = new Bar_();
bar2_.a=5;
console.log(bar_.b.x,bar_.a);

//写了三篇的prototype了 而且标题是js原型。。。。我不管反正也不是写论文出书。我就按我自己喜欢的方式叙述了。反正我写了三篇的prototype就是原型。。我也没离题。。。
//首先js的对象分成 function 和 new O__O "…。。或者就是 function和其他。。。我下面就说new 。。这玩意打字方便。。
//function 都会有一个prototype 。。但是 new 的 就没有
function Foo(){
    this.a = 0;
}

var foo = new Foo();
console.log(foo.prototype);//undefined
//所以前面的帖子 写的是 foo  Foo.prototype。。
//每个function 对象都会有一个 prototype属性 指向一个prototype对象。 prototype是一个地址,指向一个对象,就比如我这存的是你家的地址,存的这个地址指向你家。然后呢这个prototype对象又会有一个constructor属性,constructor属性是指向function本身。所以讲道理前面的继承还少了一点 修改Bar的prototype为Foo的prototype,会使得Bar的prototype.constructor是指向Foo的。介于反正执行起来没毛病只不过,也不是根据constructor来决定执行那个函数的。。就没鸟他。这个prototype指向的那个对象就是这个function的原型对象
console.log(Foo.prototype.constructor);//就是Foo函数源码。。
//这里说一句 为什么不发帖子用代码分享。。主要就是懒得弄图。。。。虽然我也知道配图会好不少。

//接下来这段话。。暂时先这么理解下好吧~。
//然后 A.prototype -> B.prototype 那么b的prototype 呢?
//以foo为例 大概就是 foo->Foo->Object->null
//所以 foo 能访问Function Object这些prototype上的所有东西
//比如
console.log(foo.toString())
//这个这么通过原型 把这些链接起来这个就是原型链。(先这么理解)









//最后在解释一点  new
//new 简单来说就是 先创建一个空的对象然后替换他原本的prototype 指向 你要new 的那个 函数的prototype,然后初始化(this的是这里就不扯了)
//最开始说过 new出来的没有 prototype 但是这里先这么理解着。具体之后的帖子再说


//前文得知 function 有 prototype 但是new出来的没有。
//但是只要是对象 就是不管是function还是new出来的(除了Object) 都会有一个叫做 __proto__的东西
//之前说过 new 就是先创建一个空对象然后 让他的prototype 指向 要创建的。然后初始化
//但是呢 new出来的东西 又没有prototype这东西。那怎么办呢?
//这时候就要用到这个叫 __proto__的东西了
//通过 new 创建对象 对象的__proto__就指向他的constructor.prototype
//通过 new 其实就是通过调用Foo这个函数整出来一个对象 这个Foo我们叫他构造器

function Foo(){
    this.a = 0;
}

var foo = new Foo();
console.log(foo.__proto__ === foo.constructor.prototype);//true;
console.log(foo.__proto__ === Foo.prototype);//true;
//Foo.constructor 就是他本身 constructor.prototype 自然就是Foo.prototype

//也可以写成

var foo_ = {}
foo_.__proto__ = Foo.prototype;
Foo.call(foo_);
//这就是new 的真面目了

console.log(foo_.a);//0
console.log(foo_.__proto__ === foo_.constructor.prototype);//true;

console.log(foo_.__proto__ === Foo.prototype);//true;



//
var bar = {
    a:1
}
console.log(bar.constructor);//现实是本机代码。。这不是咱要关心的重点 
console.log(bar.__proto__ === bar.constructor.prototype);//true
//反正就__proto__ 也是 constructor.prototype 和用new一样 和new比就是少了修改__proto__

var foo_2 = Object.create(foo);
console.log(foo_2.constructor);//就是 Foo函数本身
console.log(foo_2.__proto__ === foo_2.constructor.prototype);//false
console.log(foo_2.__proto__ === foo);//true

var foo_2 = Object.create(Foo);
console.log(foo_2.constructor);//也是本机代码。。不管他
console.log(foo_2.__proto__ === foo_2.constructor.prototype);//false
console.log(foo_2.__proto__ === Foo);//true
//总结下来 用构造器创建对象或者 直接字面量 用 {} __proto__ 都是 constructor.prototype
//var a = new A() a.constructor = A 所以 a.__proto__ === A.prototype
//但是A = Object.create(B) A.__proto__ == B
//之前我说原型链是prototype连起来的 显然感觉不对 毕竟 很多对象都没有prototype 
//但是每个对象(除了Object)都有__proto__
//实际上连起来的 是 __proto__

console.log(foo.__proto__ === Foo.prototype);//foo的构造器是 Foo 所以foo.__proto__ 就是 Foo.prototype
console.log(foo.__proto__.__proto__ === Object.prototype);//true;
console.log(foo.__proto__.__proto__.__proto__ === null)//true
//foo->Foo->Object->null

//之前的帖子 js原型(三) 和js 构造函数 继承 都已经说了继承,但都不是很好
//目前推荐使用 如下方法
function Foo(){
    this.a = 1;
    Foo.prototype.b = 2;
    Foo.prototype.show = function(){
        console.log(this.b)
    }
}

function Bar(){
    Foo.call(this);
    this.c = 3;
}

Bar.prototype = Object.create(Foo.prototype);

var bar = new Bar();

bar.show();//2

//首先执行的是 Bar.prototype = Object.create(Foo.prototype); es6 貌似有新语法 更加安全准确官方的修改。。
//但是可读性不如这个。。。而且我也写习惯了。。。。。当然你也可以用新语法
//Object.setPrototypeOf( Bar.prototype, Foo.prototype );
//执行完之后 Bar。prototype 指向了 Foo.prototype Bar的后代就能访问到 Foo.prototype的 属性b 和 函数show了。设计的时候b和show就是设计成可以共享的。函数自然不用说。属性就要视情况而定。比如一个类 人每个人都要能都遵循一样的吃饭规则,吃饭这个函数可以共用,所有人都共用一个家园 -- 地球 家园这个属性也可以共用,但是每个人都有自己的名字,名字就不能共用,好比Foo.a。
//所以Bar 执行Foo.call(this) 在Bar上创建了一个a 当然也会执行 Foo.prototype.b = 2;但是无所谓。比如我现在可以访问bar.b 结果是2。那么如果我又var bar2 = new Bar()。 还是赋值成2 那结果还是没问题。如果我手动更改bar.b的值。那么就会发生屏蔽。具体看我的ja原型(二)。这样就可以做到每次new 每个对象有各自属于自己的a和共用的b和show