【ECMAScript】继承深入、call_apply、圣杯模式、模块化

1. 原型是在构造函数之上的,又是构造函数的一个属性

一个构造函数的原型属性,是这个构造函数所有实例化出来的对象的公共祖先,所有构造函数构造出来的对象,都继承于它的原型

function Person(){

}
Person.prototype.name = 'Lucy';

var p = new Person();
console.log(p);
// 实例化出来的对象是完全继承于它的原型的

2. 原型 -> 继承 ⭐

Professor.prototype = {
  name: 'Tom',
  tSkill: 'JAVA'
}

function Professor(){}

var professor = new Professor();

// ---------------------------------
Teacher.prototype = professor;
function Teacher(){
  this.name = 'Joe';
  this.mSkill = 'JS/JQ';
}

var teacher = new Teacher();

console.log(teacher);

//------------------------------
Student.prototype = teacher;
function Student(){
  this.name = 'Lily';
  this.pSkill = 'HTML/CSS';
}

var student = new Student(); // student继承了上面所有原型的属性
console.log(student);
  1. 原型链继承 -> 存在引用值共享的问题

  2. 借用构造函数 -> 父类原型方法无法获取(覆盖了)

  3. 组合继承(伪经典继承) -> 上面两个同时使用,会出现构造函数执行两次的情况

  4. 寄生组合继承(经典继承): YUI

  5. 圣杯模式

  6. es6 class 继承

  7. 拷贝继承 -> 不常用,知道有就行

问:原型链继承存在的问题:引用值共享的问题,如何解决?为什么?⭐

答:借用构造函数解决,-> 会出现父类原型方法无法获取的问题 ⭐

原型链继承的时候,拿的是原型上的属性,原型的属性是继承而来,实例是没有办法修改原型的属性,但是只能够保证引用是对的,共享的是引用,所以修改了之后所有后代的值都会变。

借用构造函数之后,sub1和sub2拿的并不是原型上的属性,是成员属性,是构造函数自身的属性,每一次实例,都会执行一次,不会对其它实例有影响,每一次实例都是独立的

function Super(){
  this.a = [1, 2, 3, 4];
}

Super.prototype.say = function(){
  console.log(2);
}

function Sub(){
  Super.call(this); // 构造函数自身的属性,每一次实例都是独立的
}

// Sub.prototype = new Super(); // 原型链继承

Sub.prototype.say2 = function(){
  console.log(33333);
}

// 经典继承
// Object.create 是 es5 的方法, es3可能不兼容
if(!Object.create){ // 如果这个方法不存在,重写
  Object.create = function(proto) {
    var F = function(){};
    F.prototype = proto;
    return new F(); // 返回的是一个实例
  }
}
// 经典继承
Sub.prototype = Object.create(Super.prototype); 
// 相当于 Sub.prototype.__proto__ = Super.prototype
// 实例Sub.prototype的原型是Super.prototype
// 但因为 es6 不推荐 __proto__ 这种写法,所以用Object.create
// 因为没必要拿成员方法,所以直接继承原型
// 重写了Sub.prototype,原有的Sub.prototype上的属性会丢失
// -> 可以将方法写在 继承之后

var sub1 = new Sub();
var sub2 = new Sub();

sub1.a.push(5);
console.log(sub1.a);
console.log(sub2.a);

es6

class Super{
  constructor(){
    this.a = [1, 2,3,4];
  }
  say2(){
    console.log(2222);
  }
}

class Sub extends Super {
  // 静态函数的写法
  static say1() {
    console.log('1111');
  }
  // 原型上的方法法
  say(){}
}
// 上面这个方法相当于
// Sub.say1 = function(){}

var sub1 = new Sub();
var sub2 = new Sub();

// sub1.a = '3333';
sub1.a.push(5);
console.log(sub1.a);
console.log(sub2.a);

sub1.say1();
sub1.say2();


//----------------
// 静态方法
Array.from();
Object.getPrototypeof();

// 实例方法
[].sort()

3. call、apply

通过 apply 借用别人的属性和方法

Teacher.prototype.wife = 'Jane';

function Teacher(name, mSkill){
  this.name = name;
  this.mSkill = mSkill;
}

function Student(name, mSkill, age, major){
  Teacher.apply(this, [name, mSkill]); // 借用 Teacher 的属性
  this.age = age;
  this.major = major;
}

var student = new Student('Tom', 'JS/JQ', 18, 'Computer');
console.log(student);
// 公共原型
function Teacher(){
  this.name = 'Tom';
  this.tSkill = 'JAVA';
}

Teacher.prototype = {
  pSkill: 'JS/JQ'
}
var t = new Teacher();
console.log(t);

function Student(){
  this.name = 'Annie'
}
Student.prototype = Teacher.prototype; // 继承Teacher的原型
Student.prototype.age = 18; // 这里更改Student的prototype,Teacher 的 prototype 也会被修改

var s = new Student();
console.log(s);

4. 圣杯模式

解决更改 Student 的 prototype,Teacher 的 prototype 也会被修改的问题:添加一个缓冲构造函数

圣杯模式:圣杯指bufer

function Teacher(){
  this.name = 'Tom';
  this.tSkill = 'JAVA';
}

Teacher.prototype = {
  pSkill: 'JS/JQ',
  obj: {a: 1}
}

var t = new Teacher();
console.log(t);

function Student(){
  this.name = 'Annie'
}

// 缓冲构造函数
function Buffer(){}

Buffer.prototype = Teacher.prototype;
var buffer = new Buffer();
console.log(buffer);

Student.prototype = buffer; // 相当于继承 Buffer 的原型,继承过来的东西不能修改
Student.prototype.obj.a = 4; // 问题:修改引用值可以修改成功,为什么❓
Student.prototype.age = 18; // 这时候修改Student的prototype 和Teacher的prototype没有关系了

var s = new Student();
console.log(s);

5. CSS 圣杯模式 双飞翼

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    /* 清除浮动 */
    .clearfix::after{
      content: '';
      display: table;
      clear: both;
    }

    .wrap {
      width: 700px;
      margin: 0 auto;
      border: 1px solid #000;
    }

    .top,
    .foot {
      height: 50px;
      background-color: #000;
    }

    .main {
      padding: 0 100px;
      overflow: hidden;
    }

    .main .left,
    .main .content,
    .main .right {
      float: left;
      position: relative;
      background-color: green;
      /* 圣杯模式主要逻辑是赋值 */
      margin-bottom: -2000px;  
      padding-bottom: 2000px;
    }

    .main .left {
      left: -100px;
      width: 100px;
    }

    .main .content {
      width: 100%;
      margin-left: -100px;
      background-color: red;
    }

    .main .right {
      left: 100px;
      width: 100px;
      margin-left: -100px;
    }
  </style>
</head>

<body>
  <div class="wrap">
    <div class="top"></div>
    <div class="main clearfix">
      <div class="left">123</div>
      <div class="content">234<br />234</div>
      <div class="right">123</div>
    </div>
    <div class="foot"></div>
  </div>
  <script type="text/javascript"></script>
</body>

</html>

6. 闭包

普通闭包

function test(){
    var num = 0; // 私有变量
    
    function add(){
        num++;
        console.log(num);
    }
    return add;
}

var add = test();
add();
add();
add();

通过对象返回闭包

function test(){
    var num = 0; // 私有变量
    
    var compute = {
        add: function(){
            num++;
            console.log(num);
        }
    }
    return compute;
}

var compute = test();
compute.add();
compute.add();
compute.add();

构造函数形成闭包

function Compute(){
    var num = 0;
    this.add = function(){
        num++;
        console.log(num);
    }
    // 这里隐式的 return this 了
}
var compute = new Compute();
compute.add();

7. 以闭包的方式包装圣杯模式

如何将圣杯模式像闭包的方式包装一下 ⭐

// 企业级写法  模块化开发
var inherit = (function test(){var Buffer = function(){}; // 有自己的命名空间了
    return function(Target, Origin){
        Buffer.prototype = Origin.prototype;
        Target.prototype = new Buffer(); // new 一定要放在 prototype 赋值的下方
        Target.prototype.constructor = Target; // 还原构造器
        Target.prototype.super_class = Origin; // 继承源
    }
})();


inherit(Student, Teacher);

Teacher.prototype.name = 'Tom';

function Teacher(){}
function Student(){}
function Buffer(){}

Student.prototype.age = 18
var s = new Student();
var t = new Teacher();
console.log(s);
console.log(t);

例1

var inherit = (function test(){ 
    var Buffer = function(){}; // 有自己的命名空间了
    return function(Target, Origin){
        Buffer.prototype = Origin.prototype;
        Target.prototype = new Buffer(); // new 一定要放在 prototype 赋值的下方
        Target.prototype.constructor = Target; // 还原构造器
        Target.prototype.super_class = Origin; // 继承源
    }
})();

// 写自启动函数,是因为加载的时候就需要启动,把里面的函数return 给变量initProgrammer保存
// 让 initProgrammer 接收,是因为不想里面抛出来的函数立即执行,让它等待着,等需要执行的时候再执行
var initProgrammer = (function(){ // 在这个函数里面造了一个伪全局,避免了全局污染
    var Programmer = function(){};
    Programmer.prototype = {
        name: '程序员',
        tool: '计算机',
        work: '编写应用程序',
        duration: '10个小时',
        say: function(){
            console.log('我是一名' + this.myName + this.name + ',我的工作是用' + this.tool + this.work + ', 我每天工作' + this.duration + ',我的工作需要用到' + this.lang.toString() + '。');
        }
    }
    function FrontEnd(){}
    function BackEnd(){}
    
    inherit(FrontEnd, Programmer);
    inherit(BackEnd, Programmer);
    
    FrontEnd.prototype.lang = ['HTML', 'CSS', 'JavaScript'];
    FrontEnd.prototype.myName = '前端';
    
    BackEnd.prototype.lang = ['Node', 'Java', 'SQL'];
    BackEnd.prototype.myName = '后端';
    
    return {
        FrontEnd: FrontEnd,
        BackEnd: BackEnd
    }
})();

var frontEnd = new initProgrammer.FrontEnd();
var backEnd = new initProgrammer.BackEnd();

frontEnd.say();
backEnd.say();

例2

window.onload = function(){
    init();
}

function init(){
    initCompute();
    initFunctions();
}

// 按需启动功能
var initCompute = (function(){
    var a = 1,
        b = 2;
    function add(){
        console.log(a + b);
    }
    function minus(){
        console.log(a - b);
    }
    function mul(){
        console.log(a * b);
    }
    function div(){
        console.log(a / b);
    }
    return function(){
        add();
        minus();
        mul();
        div()
    }
})();

var initFunctions = (function(){
    
})();


// 或者用插件化开发
;(function(){
    var Slider = function(opt){}
    Slider.prototype = {
        
    }
    window.Slider = Slider;
})();
var slider = new Slider({
    
});

8. 作业

整合下面3个功能(模块化开发:独立的作用域空间,将抛出来的函数保存到GO)

打印100以内的能被 3 或 5 或7 整除的数

打印斐波那切数列的第n位

打印从0到一个数的累加值

window.onload = function(){
  init();
}

function init(){
  initFb(10);
  initDiv(100);
}

var initFb = (function(){
    function fb(n) {
      if(n <= 0) {
        return 0;
      }
      if(n <= 2){
        return 1;
      }
      return fb(n - 1) + fb(n - 2);
    }
})();

var initDiv = (function(){
  function div(n) {
    for(var i = 0; i <= n; i++){
      if(i % 3 === 0 || i % 5 === 0 || i % 7 === 0) {
        arr.push(i);
      }
    }
    return arr;
  }
  return div;
})();

var acc = (function(){
    return function(){
      var num = 0;
      function add(){
        console.log(++num);
      }
      return add;
    }
})()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值