InterviewMap —— Javascript (三)

1、深刻理解 javascript 的 this 绑定

this问题的由来javascript

var people = {
    Name: "海洋饼干",
    getName : function(){
        console.log(this.Name);
    }
};
var bar = people.getName;

bar();    // undefined
复制代码
var people = {
    Name: "海洋饼干",
    getName : function(){
        console.log(this.Name);
    }
};
var bar = people.getName;
var Name = "zjj";
bar(); // zjj
复制代码

上面两个问题,分别获得的记过是 undefinedzjj。是否是以为很别扭,下面咱们就去温习一下this的绑定规则吧。html

一、this出现的意义

内存的数据结构java

var obj = {foo: 5}
复制代码

原始的对象以字典结构保存,每个属性名都对应一个属性描述对象。举例来讲,上面例子的foo属性,其实是如下面的形式保存的。web

函数面试

因为函数是一个单独的值,因此但是在不一样的环境中调用它。数组

var f = function () {};
var obj = { f: f };

// 单独执行,全局下
f()

// obj 环境下执行
obj.f()
复制代码

因为函数能够在不一样的环境中执行,因此须要一种机制能够,在函数内部访问到当前的运行环境。因此this就出现了。bash

以下面的栗子。函数可使用不一样的运行环境,致使内部使用的x的值是变化的。数据结构

var f = function () {
  console.log(this.x);
}

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// 单独执行
f() // 1

// obj 环境执行
obj.f() // 2
复制代码

因此这就是this存在的意义了。闭包

二、this绑定规则

牢记准则: this指向什么,彻底取决于什么地方以什么方式调用,而不是建立时。app

4种绑定的规则以下:

(1)、默认绑定

function foo(){
    var a = 1 ;
    console.log(this.a);    // 10
    // window.a
}
var a = 10;
foo(); // 默认是window
复制代码

(2)、隐性绑定

function foo(){
    console.log(this.a);
}

var obj = {
    a : 10,
    foo : foo  // 必须有这条
}

foo();                // undefined

obj.foo();            // 10
复制代码

这里的obj.foo()就是隐性绑定了。若是是链性的关系,好比 xx.yy.obj.foo();, 上下文取函数的直接上级,即紧挨着的那个,或者说对象链的最后一个(也便是obj了)。

(3)、显性绑定

隐性质绑定的限制: 隐性绑定中有一个致命的限制,就是上下文必须包含咱们的函数 ,例:var obj = { foo : foo },若是上下文不包含咱们的函数用隐性绑定明显是要出错的。

显性绑定,js 给咱们提供的函数 callapply,它们的做用都是改变函数的this指向,第一个参数都是 设置this对象。

使用call和apply

function foo(a,b){
    console.log(a+b);
}
foo.call(null,'海洋','饼干');        // 海洋饼干 这里this指向不重要就写null了
foo.apply(null, ['海洋','饼干'] );     // 海洋饼干
复制代码

使用bind

var obj = {
    a: 10
}

function foo () {
    console.log(this.a);
}

foo = foo.bind(obj);

foo();  // 10
复制代码

修改上面的那个栗子:

function f (){
    cosnole,log(this.a);
};

var obj = {
    a: 11
}

f.call(obj);

f(); // 11
复制代码

这样显然没有了上面所说的隐性的弊端了。

(4)、new 绑定

function foo(){
    this.a = 10;
    console.log(this);
}
foo();                    // window对象
console.log(window.a);    // 10 默认绑定

var obj = new foo();      // foo{ a : 10 } 建立的新对象的默认名为函数名
                          // 而后等价于 foo { a : 10 }; var obj = foo;
console.log(obj.a);       // 10 new绑定
复制代码

函数与new一块使用即构造函数,this指向新建立的对象

总结

  • 若是函数被new 修饰
this绑定的是新建立的对象,例:var bar = new foo(); 函数 foo 中的 this 就是一个叫foo的新建立的对象 , 而后将这个对象赋给bar , 这样的绑定方式叫 new绑定 .
复制代码
  • 若是函数是使用call,apply,bind来调用的
this绑定的是 call,apply,bind 的第一个参数.例: foo.call(obj); , foo 中的 this 就是 obj , 这样的绑定方式叫 显性绑定 .
复制代码
  • 若是函数是在某个 上下文对象 下被调用
this绑定的是那个上下文对象,例 : var obj = { foo : foo }; obj.foo(); foo 中的 this 就是 obj . 这样的绑定方式叫 隐性绑定 .
复制代码
  • 若是都不是,即便用默认绑定
例:function foo(){...} foo() ,foo 中的 this 就是 window.(严格模式下默认绑定到undefined).
   这样的绑定方式叫 默认绑定 .
复制代码

三、箭头函数中的this

function a() {
    return () => {
        return () => {
        	console.log(this)
        }
    }
}
console.log(a()()())
复制代码

箭头函数实际上是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,由于调用 a 符合前面代码中的第一个状况,因此 this 是 window。而且 this 一旦绑定了上下文,就不会被任何代码改变。

2、闭包

一、认识闭包

闭包的定义: 闭包是指那些能够访问自由变量的函数。

自由变量: 指能够在函数中访问,但并非函数参数也不是局部变量的变量

闭包 = 函数 + 自由变量

var a = 1;

function foo() {
    console.log(a);
}

foo();
复制代码

上诉栗子也知足上面的要求,因此他也是一个闭包。但是你会发现,他怎么和咱们平时所见到的不同呢?

在《JavaScript权威指南》中就讲到:从技术的角度讲,全部的JavaScript函数都是闭包。

其实这是理论上的闭包,并非实践中的闭包,咱们平时碰见的都是实践中的闭包。

ECMAScript中,闭包指的是:

- 理论上: 全部的函数都是闭包。由于每个函数建立的时候都会保存上层上下文中的变量对象保存起来。存放到做用域链中,即使是全局变量也是。

- 实践上: 只有知足下列条件的才是:
    即使建立他的执行上下文已经被销毁了,可是他仍然存在
    在代码中引用了自由变量
复制代码

咱们先从栗子入手吧:

var scope = "global scope";
function checkscope(){
    var scope = "local scope"; // 内部变量
    
    function f(){
        // 引用自由变量 
        return scope;
    }
    // 返回出去,表示仍然存在
    return f;
}

var foo = checkscope();
foo();
复制代码

首先咱们要分析一下这段代码中执行上下文栈和执行上下文的变化状况。

第一步 建立全局执行上下文并压入执行上下文栈

ECStack = [
    globalContext
];
复制代码

第二步 全局执行上下文对象

globalContext.VO = {
    scope: undefined,
    checkscope: function(){},
    foo: undefined
}
复制代码

第三步 全局执行,建立checkscope函数

globalContext.VO = {
    scope: global scope,
    checkscope: function(){},
    foo: undefined
}

checkscope.[[scope]] = {
    globalContext.VO
}
复制代码

第三步 建立函数上下文,并压入执行上下文栈

// 函数执行上下文建立好后,压入执行上下文栈中
ECStack = [
    checkscopeContext,
    globalContext
];
复制代码

第四步 函数并不会立马执行,首先是建立函数的scope

checkscopeContext.scope = checkscope.[[scope]]
复制代码

第五步 建立活动对象AO

checkscopeContext.AO = {
    arguments: {
        length: 0
    },
    scope: undefined
}
复制代码

第六步 将AO插入到做用域链中

checkscopeContext = {
    AO = {
        arguments: {
            length: 0
        },
        scope: undefined,
        f: function(){}
    },
    scope: [AO, checkscope.[[scope]]]
}
复制代码

第七步 开始执行checkscope函数

checkscopeContext = {
    AO = {
        arguments: {
            length: 0
        },
        scope: 'local scope',
        f: function(){}
    },
    scope: [AO, checkscope.[[scope]]]
}


复制代码

第八步 checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出

ECStack = [
    globalContext
];
复制代码

第九步 继续执行全局上下文的代码,开始建立和执行f函数,首先建立函数上下文,插入到执行上下文栈中。

f.[[scope]] = checkscopeContext.AO

ECStack = [
    fContext,
    globalContext
];


复制代码

第十步 开始执行f函数,可是并非立马执行,首先建立AO对象和scope

fContext.AO = {
    arguments: {
        length: 0
    },
    scope: [AO, checkscopeContext.AO, globalContext.VO]
}
复制代码

第十步 开始真正执行f函数,返回一个值,执行完毕以后,将该函数弹出栈

ECStack = [
    globalContext
];
复制代码

了解到这个过程,咱们应该思考一个问题,那就是:

当 f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 做用域下的 scope 值呢?
复制代码

js是能够的,由于在f的scope中,Scope: [AO, checkscopeContext.AO, globalContext.VO],。维护了一个做用域链。虽说checkscope函数已经被销毁了,可是因为f维护本身的做用域链,而且使用了上一层的自由变量,即便已经被销毁,可是checkscopeContext.AO对象却会驻留在内存中,因此天然咱们的f就能访问上层变量了。

二、面试必刷题

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0](); // 3
data[1](); // 3
data[2](); // 3
复制代码

缘由:

全局上下文VO对象为:

VO = {
    data: [...],
    i: 3
}
复制代码

当执行 data[0] 函数的时候,data[0] 函数的做用域链为:

data[0]Context = {
    Scope: [AO, globalContext.VO]
}
复制代码

此时访问i的话,访问的都是全局中的,所以都是输出3.

因此让咱们改为闭包看:

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
    return function() {
       console.log(i); 
    }
  })(i);
}

data[0](); // 0
data[1](); // 1
data[2](); // 2
复制代码

此时data[0]的做用域就发生了变化了:

data[0]Context = {
    Scope: [AO, 匿名函数Context.AO ,globalContext.VO]
}
复制代码

匿名函数中的AO为:

AO: {
    arguments: {
        0: 0,
        length: 1
    },
    i: 0
}
复制代码

data[0]Context 的 AO 并无 i 值,因此会沿着做用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即便 globalContext.VO 也有 i 的值(值为3),因此打印的结果就是0。

三、如何建立闭包

建立闭包最多见方式,就是在一个函数内部建立另外一个函数,并返回。

function foo (){
    var a = 10;
    
    function bar(){
        return a;
    }
    
    return bar;
}

var result = foo();
result(); // 10
复制代码

四、闭包的好处

一、 累加 【减小全局变量个数】

function add () {
  var a  = 0;
  return function () {
    a++;
console.log(a);
  }
}

var result = add();
result(); // 1
result(); // 2
复制代码

二、封装

var http = function () {
  // 局部变量
  var host = "http://..";

  // 局部变量
  var admin = function(){
    // 局部
    var url = host + "/login"
    return {
      login : function() {
        return 1;
      }
    }
  }();

  return {
    admin
  }
}();

http.admin.login();
复制代码

五、闭包的使用注意

一、 对捕获的变量只是个引用,不是复制

二、每调用一次父函数,就会产生一个新的闭包

function f() {
  var num = 1;
  return function () {
    num++;
    alert(num);
  }
}

var result1 = f();
result1(); // 2
result1(); // 3

var result2 = f();
result2(); // 2
result2(); // 3
复制代码

三、循环

<ul>
    <li id="1">1</li>
    <li id="2">2</li>
    <li id="3">3</li>
  </ul>
  <script>
    for(var i = 1 ; i <= 3; i++) {
      var el = document.getElementById(i);
      el.onclick = function() {
        alert(i);
      }
    }
  </script>

  // 结果是不管点击那个,都是弹出4
复制代码
// 解决办法
<ul>
    <li id="1">1</li>
    <li id="2">2</li>
    <li id="3">3</li>
  </ul>
  <script>
    for(var i = 1 ; i <= 3; i++) {
      var el = document.getElementById(i);
      el.onclick = (function(id) {
        return function () {
          alert(id);
        }
      })(i);;
    }
  </script>
复制代码

高效使用 JavaScript 闭包

3、js参数按值传递

在《JavaScript高级程序设计》第三版 4.1.3,讲到传递参数:

ECMAScript中全部函数的参数都是按值传递的。

按值传递: 把外面的值,复制一份传给函数做为参数使用。就和把值从一个变量复制到另外一个变量同样。其实就是创建了一份备份。

js中有基本类型和引用类型。基本类型是存在与栈中的,引用类型是存在于堆中的,可是它的引用地址倒是存在栈中的。

一、按值传递

var value = 1;
function foo(v) {
    v = 2;
    console.log(v); //2
}
foo(value);
console.log(value) // 1
复制代码

很好理解,当传递 value 到函数 foo 中,至关于拷贝了一份 value,假设拷贝的这份叫 _value,函数中修改的都是 _value 的值,而不会影响原来的 value 值。

参照上图来分析:

_value 是 value 的一个备份,或者说是副本,就至关于 _value = value 。此时 _value = 2; 改变了,可是 value 并无变。

二、引用传递

var obj = {
    value: 1
};
function foo(o) {
    o.value = 2;
    console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2
复制代码

所谓按引用传递,就是传递对象的引用,函数内部对参数的任何改变都会影响该对象的值,由于二者引用的是同一个对象。

针对官方的解释咱们来分析:

ECMAScript中全部函数的参数都是按值传递的。

也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另外一个变量同样。基本类型的传递如同基本类型的复制同样,而引用类型值的传递,如同引用类型变量的复制同样。
复制代码
  • 总结

很简单,javascript函数参数都是按值传递(都是栈内数据的拷贝)。 基本类型传的是值自己(由于直接把值存在栈内),引用类型传的是对象在内存里面的地址 (由于复杂对象存在堆内,因此在栈里存对象所在的堆地址)。 这种引用类型的传递其实也是传递的值,就是堆地址的引用。

4、call和apply的模拟实现

一、call和apply的区别

callapply都是为了解决改变 this 的指向。做用都是相同的,只是传参的方式不一样。

除了第一个参数外,call 能够接收一个参数列表,apply 只接受一个参数数组。

let a = {
    value: 1
}
function getValue(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value)
}

getValue.call(a, 'zjj', 12);
getValue.apply(a, ['zjj', 12]);
复制代码

二、模拟实现 call 和 apply

【1】模拟实现 call

第一步

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
};

foo.bar(); // 1
复制代码

若是咱们设计成这样子,就能够实现了。可是咱们为foo对象又添加了一个属性有点很差呢,不要紧,咱们能够delete嘛。

因此咱们模拟的步骤能够分为:

将函数设为对象的属性
执行该函数
删除该函数
复制代码

测试案例:

var obj = {
    a: 1
}

function foo(){
    console.log(this.a);
}

foo.call_new(obj);
复制代码

实现:

// 初版
Function.prototype.call_new =  function(context){
    // 首先要获取调用call的函数,用this能够获取
    context.fn = this;
    context.fn();
    delete context.fn;
}
复制代码

第二步

call 函数还能给定参数执行函数。举个例子:

var obj = {
    a: 1
}

function foo(name){
    console.log(name);  // zjj
    console.log(this.a) // 1
};

foo.call(obj, 'zjj');
复制代码
Function.prototype.call_new = function (context) {
      // 首先要获取调用call的函数,用this能够获取
      context.fn = this;
      var args = [];
      for (var i = 1; i < arguments.length; i++) {
        args.push('arguments[' + i + ']');
      }
      eval('context.fn(' + args + ')');
      delete context.fn;
    }

    var obj = {
      a: 1
    }

    function foo(name) {
      console.log(name);
      console.log(this.a);
    }

    foo.call_new(obj, 'zjj');
复制代码

第三步

还有小问题须要解决,this为null的时候,默认是window。并且函数可能会有返回值的。

Function.prototype.call_new = function (context) {
      var context = context || window;
      // 首先要获取调用call的函数,用this能够获取
      context.fn = this;
      var args = [];
      for (var i = 1; i < arguments.length; i++) {
        args.push('arguments[' + i + ']');
      }
      var result = eval('context.fn(' + args + ')');
      delete context.fn;
      return result;
    }

    var obj = {
      a: 1
    }

    function foo(name) {
      console.log(name);
      console.log(this.a);
      return {
        name: name,
        value: this.a
      }
    }

    console.log(foo.call_new(obj, 'zjj'));
复制代码
// ES6
Function.prototype.call_new = function (context) {
      var context = context || window;
      // 首先要获取调用call的函数,用this能够获取
      context.fn = this;
      var args = [];
      args = [...arguments].slice(1);
      var result = context.fn(...args);
      delete context.fn;
      return result;
    }
复制代码

【2】模拟实现 apply

Function.prototype.call_new = function (context, arr) {
      var context = Object(context) || window;
      // 首先要获取调用call的函数,用this能够获取
      context.fn = this;
      let result;
      if (!arr) {
        result = context.fn();
      } else {
        var args = [];
        for (var i = 0; i < arr.length; i++) {
          args.push('arr['+i+']') ;
        }
        // 这里会自动调用toString方法
        result = eval('context.fn(' + args + ')');
      }

      delete context.fn;
      return result;
    }

    var obj = {
      a: 1
    }

    function foo(name) {
      console.log(name);
      console.log(this.a);
      return {
        name: name,
        value: this.a
      }
    }

    console.log(foo.call_new(obj, ['zjj']));
复制代码
// 使用 ES6
Function.prototype.call_new = function (context, arr) {
      var context = Object(context) || window;
      // 首先要获取调用call的函数,用this能够获取
      context.fn = this;
      let result;
      if (!arr) {
        result = context.fn();
      } else {
        result = context.fn(...arr);
      }

      delete context.fn;
      return result;
    }
复制代码

【2】模拟实现 bind

第一步

Function.prototype.bind1 = function (context) {
      var self = this;
      return function () {
        return self.apply(context)
      }
    }

    var obj = {
      a: 1
    }

    function foo() {
      console.log(this.a);
    }
    var result = foo.bind(obj);
    result(); // 1
复制代码

第二步

处理参数的传递

Function.prototype.bind1 = function (context) {
        var self = this;
        var args = Array.prototype.slice.call(arguments, 1); // 获得参数
        return function () {
          var args1 = Array.prototype.slice.call(arguments);

          return self.apply(context, args.concat(args1));
        }
      }

      var obj = {
        a: 1
      }

      function foo(name, age) {
        console.log(name);
        console.log(age);
        console.log(this.a);
      }
      var result = foo.bind(obj, 'zjj');
      result(18);
复制代码

5、数组对象和arguments

一、认识类数组对象

var array = [1,2,3,4,5];

var arrayLike = {
    0: '1',
    1: 4,
    2: 'zjj',
    length: 3
}
复制代码

拥有一个 length 属性和若干索引属性的对象

那让咱们从读写、获取长度、遍历三个方面看看这两个对象。

读写

console.log(array[0]); // 1
  console.log(arrayLike[0]); // 1

  array[0] = "lll";
  arrayLike[0] = "mmm";

  console.log(array[0]); // lll
  console.log(arrayLike[0]); // mmm
复制代码

获取长度

console.log(array.length); // 5
  console.log(arrayLike.length); // 3
复制代码

遍历:

for (var i = 0, len = array.length; i < len; i++) {
    console.log(array[i]);
  }
  for (var i = 0, len = arrayLike.length; i < len; i++) {
    console.log(arrayLike[i]);
  }
复制代码

就上面的状况,二者是很是的像。

二、类数组对象调用数组的方法

没法直接调用,只能是Function.call间接调用。

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }

Array.prototype.join.call(arrayLike, '&'); // name&age&sex

Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] 
// slice能够作到类数组转数组

Array.prototype.map.call(arrayLike, function(item){
    return item.toUpperCase();
}); 
// ["NAME", "AGE", "SEX"]
复制代码

三、类数组转数组

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
// 1. slice
Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"] 
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"] 
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"] 
// 4. apply
Array.prototype.concat.apply([], arrayLike)
复制代码

要说到类数组对象,Arguments 对象就是一个类数组对象。在客户端 JavaScript中,一些DOM方法(document.getElementsByTagName()等)也返回类数组对象。

四、arguments对象

arguments对象只存在函数体中,包括了参数和其余的属性。箭头函数并无arguments对象。

function foo(name, age, sex) {
    console.log(arguments);
}

foo('name', 'age', 'sex')
复制代码

arguments 和对应参数的绑定

function foo(name, age, sex, hobbit) {

    console.log(name, arguments[0]); // name name

    // 改变形参
    name = 'new name';

    console.log(name, arguments[0]); // new name new name

    // 改变arguments
    arguments[1] = 'new age';

    console.log(age, arguments[1]); // new age new age

    // 测试未传入的是否会绑定
    console.log(sex); // undefined

    sex = 'new sex';

    console.log(sex, arguments[2]); // new sex undefined

    arguments[3] = 'new hobbit';

    console.log(hobbit, arguments[3]); // undefined new hobbit

}

foo('name', 'age')
复制代码

总之:传入的参数,实参和 arguments 的值会共享,当没有传入时,实参与 arguments 值不会共享

除此以外,以上是在非严格模式下,若是是在严格模式下,实参和 arguments 是不会共享的。

arguments传递参数给别的函数

// 使用 apply 将 foo 的参数传递给 bar
function foo() {
    bar.apply(this, arguments);
}
function bar(a, b, c) {
   console.log(a, b, c);
}

foo(1, 2, 3)
复制代码

强大的ES6

function func(...arguments) {
    console.log(arguments); // [1, 2, 3]
}

func(1, 2, 3);
复制代码

使用ES6的 ... 运算符,咱们能够轻松转成数组。

累数组对象的应用

若是要总结这些场景的话,暂时能想到的包括:

参数不定长
函数柯里化
递归调用
函数重载
复制代码

6、建立对象的多种方式以及优缺点

建立对象的不一样方式:

一、工厂模式

function createObj (name, age){
        var o = {};
        o.name = name;
        o.age = age;
        o.getName = function(){
          return this.name;
        }
        return 0;
      }

      createObj('zjj');
复制代码

缺点:对象没法识别,由于全部的实例都指向一个原型

二、构造函数模式

function Person(name) {
    this.name = name;
    this.getName = function () {
        console.log(this.name);
    };
}

var person1 = new Person('kevin');
复制代码

缺点:每次建立实例时,每一个方法都要被建立一次

三、原型模式

function Person(name) {

}

Person.prototype.name = 'keivn';
Person.prototype.getName = function () {
    console.log(this.name);
};

var person1 = new Person();
复制代码

优势:解决了每一个实例的建立都要从新建立一遍方法的缺点 缺点:可是1. 全部的属性和方法都共享 2. 不能初始化参数

四、上面原型模式的改进

function Person(name) {

}

Person.prototype = {
    constructor: Person,
    name: 'kevin',
    getName: function () {
        console.log(this.name);
    }
};

var person1 = new Person();
复制代码

优势: constructor有了 缺点: 属性都共享,很差初始化

五、组合模式【推荐使用】

function Person(name) {
    this.name = name;
}

Person.prototype = {
    constructor: Person,
    getName: function () {
        console.log(this.name);
    }
};

var person1 = new Person();
复制代码

六、 寄生构造函数模式【特殊使用】

function Person(name) {

    var o = new Object();
    o.name = name;
    o.getName = function () {
        console.log(this.name);
    };

    return o;

}

var person1 = new Person('kevin');
console.log(person1 instanceof Person) // false
console.log(person1 instanceof Object)  // true
复制代码

这样方法能够在特殊状况下使用。好比咱们想建立一个具备额外方法的特殊数组,可是又不想直接修改Array构造函数,咱们能够这样写:

function SpecialArray() {
    var values = new Array();

    for (var i = 0, len = arguments.length; i < len; i++) {
        values.push(arguments[i]);
    }

    values.toPipedString = function () {
        return this.join("|");
    };
    return values;
}

var colors = new SpecialArray('red', 'blue', 'green');
var colors2 = SpecialArray('red2', 'blue2', 'green2');


console.log(colors);
console.log(colors.toPipedString()); // red|blue|green

console.log(colors2);
console.log(colors2.toPipedString()); // red2|blue2|green2
复制代码

7、继承的方式

一、原型链继承

function Parent(name) {
    this.name = name || 'kiven';
}

Parent.prototype.getName = function () {
    return this.name;
}

function Child(){

}

// 这里是原型链继承的标志:
Child.prototype = new Parent('zzz');

var c = new Child();
console.log(c.getName());// zzz
复制代码

存在的问题:

1、 不能向父级传值。
2、 引用类型的实例被多个实例共享,以下:

function Parent () {
    this.names = ['kevin', 'daisy'];
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy", "yayu"]
复制代码

直接将父类的实例new Parent()赋给了子类的原型,其实子类的实例child1,child2自己是一个彻底空的对象,全部的属性和方法都得去原型链上去找,于是找到的属性方法都是同一个。

二、构造函数模式

function Person (name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function(){
    console.log('hello, my name is ' + this.name);
};
function Man(name, age) {
    Person.apply(this, arguments);
}
//Man.prototype = new Person('pursue');
var man1 = new Man('joe');
var man2 = new Man('david');
console.log(man1.name === man2.name);//false
man1.say(); //say is not a function
复制代码

这里子类的在构造函数里利用了apply去调用父类的构造函数,从而达到继承父类属性的效果,比直接利用原型链要好的多,至少每一个实例都有本身那一份资源,可是这种办法只能继承父类的实例属性,于是找不到say方法,为了继承父类全部的属性和方法,则就要修改原型链,从而引入了组合继承方式。

三、组合继承模式

function Parent(name, age) {
    this.name = name || 'kiven';
    this.age = age;
}

Parent.prototype.getName = function () {
    return this.name;
}

function Child(name){
    Parent.call(this, name);
    // 父类绑定 this 
}

// 子类原型链继承
Child.prototype = new Parent();
Child.prototype.constructor = Child;

var c = new Child('kkk');
c.age = 10;
console.log(c.name);
console.log(c.age);
var c1 = new Child('llll');
console.log(c1.name);
console.log(c1.age);


// 调用父类的方法
console.log(c1.getName());
复制代码

组合模式是 JavaScript 中最经常使用的继承模式。

function Person (name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function(){
    console.log('hello, my name is ' + this.name);
};
function Man(name, age) {
    // [1]
    Person.apply(this, arguments);
}
// [2]
Man.prototype = new Person();
var man1 = new Man('joe');
var man2 = new Man('david');
console.log(man1.name === man2.name);//false
console.log(man1.say === man2.say);//true
man1.say(); //hello, my name is joe

复制代码

这时候要当心没有覆盖掉的方法,由于他们是公有的。

三、寄生组合继承【最流行、最经典的继承方式】

function Person(name, age) {
        this.name = name;
        this.age = age;
      }
      Person.prototype.say = function() {
        console.log("hello, my name is " + this.name);
      };


      function Man(name, age) {
        // 属性继承 
        Person.apply(this, arguments);
      }

      Man.prototype = Object.create(Person.prototype);  //a. 
      Man.prototype.constructor = Man;                  //b. 有助于instanceof的判别


      var man1 = new Man("pursue");
      var man2 = new Man("joe");

      console.log(man1.say == man2.say); // true
      console.log(man1.name == man2.name); // false
复制代码

a对子类的原型对象和父类的原型对象作了很好的链接。并不像原来直接对父类原型的暴力继承,(如Man.prototype = new Person();),这样只是对属性进行了暴力的覆盖,还致使共享。

相关文章
相关标签/搜索