美文网首页JavaScript
一起来聊聊JavaScript核心中的"函数类&quo

一起来聊聊JavaScript核心中的"函数类&quo

作者: 长梦未央 | 来源:发表于2017-10-28 15:37 被阅读11次

此为js入门级文章!衔接上一篇
聊一下JavaScript中"对象修饰模式"的个人理解
本文讨论一下类的最简单实现方式。类是一种可用于创建大量相似对象的强大的函数形式。
我们还看上篇的例子,在修饰器模型之上再进一步

var carlike = function(obj, loc) { 
  obj.loc = loc; 
    obj.move = function() { 
    obj.loc++; 
  }; 
  return obj;
};
var amy = carlike({},1);
amy.move();
var ben = carlike({},1);
ben.move();

由于我们是在修饰一个全新的空对象,看起来似乎我们在一开始就可以直接使用函数carlike来创建这个对象,这里就是需要类的时候了。这里的修饰器和类之间的唯一区别就是,类创建这个需要修饰的对象,而修饰器将需要修饰的对象作为输入参数接收。因此如果我们将对象创建的过程移入函数carlike,并且将用于这个局部变量的函数参数obj移出,我们就可以得到一个类,我们给它另一种命名Car以反映它的本质。

var Car = function(loc) { 
  var obj.loc = {loc: loc}; 
  obj.move = function() { 
    obj.loc++; 
  }; 
  return obj;
};
var amy = Car(1);
amy.move();
var ben = Car(1);
ben.move();

类是一个构造器,它能够创建大量遵从大致相同模式的相似对象。按照惯例,类名通常为首字母大写的名词,类似某类事物的名词表示。创建这些相似对象的函数被称为构造函数。因为构造函数的工作就是构造同一类的对象。也就是说,类是指你要创建的某一类事物,以及创建这类事物所需的代码。构造器仅仅是用来为这些类创建实例的。这些调用构造函数返回的对象被称为实例,类的实例。当我们调用构造函数来创建一个实例时,这个操作被称为实例化。现在我们就得到了一个函数类模式的示例。因为这有一个创建类Car实例的简单函数。
但是函数类模式的基本形式会导致重复方法。正如上一篇的修饰器模式一样,为Car添加属性.move的这行代码,在每次调用构造函数Car时 都会创建一个全新的函数。我们更希望只有一个函数对象能被所有Car对象共享,以节省内存。为了避免生成那么多重复的move方法,我们要将这些代码移出构造函数Car.

var Car = function(loc) { 
   var obj.loc = {loc: loc}; 
   obj.move = move;
   return obj;
};
var move = function(){
   obj.loc++;
};
var amy = Car(1);
amy.move();
var ben = Car(1);
ben.move();

这样解释器就只访问这段代码一次,从而只创建一个被所有实例共享的函数,而非每次调用Car时都创建一个新的。但是注意,如果我们将方法move的定义移出这个构造函数,那么这个函数就无法访问闭包作用域中的变量obj,而变量obj时我们存储Car的实例的地方,因此这样行不通 move 无法正常工作。因此我们需要用参数"this"来替代obj

var move = function(){ this.loc++;};

参数this会将调用时点符号左侧的对象当作函数输入参数,从而它也成为引用这个对象时可使用的名称。
现在我们已经实现了包含共享方法的函数类模式,或简称函数共享模式。但还需要一些清理工作以完全重构。其中一点,我们可以看到有两个需要命名方法move的地方,一个是一开始创建函数的时候,另一个是我们要将其分配到每个car对象的时候。因此如果我们要创建更多函数并分配为对象的属性时,我们需要再两个不同的地方命名。

var Car = function(loc) { 
  var obj.loc = {loc: loc}; 
  obj.move = move; 
  obj.on = on; 
  obj.off = off; return obj;
};
var move = function(){ this.loc++;};
var on = function(){/*...*/};
var off = function(){/*...*/};

这样Car对象就会有两个包含所有方法的列表。这会带来很大问题,因为你极有可能修改了一处,却忘了另一处。也许我们可以使用某种编程方法遍历,要添加到每个Car对象上的所有方法。并将它们自动添加给对象,并收集到某个列表中。不一定有办法遍历作用域中所有的变量,因此需要一个更聪明的办法。也许我们可以将计划添加到Car对象的所有方法一开始就存储在某个对象中。这样就可以轻松通过程序方法遍历这个对象。这样就只需一行代码,把它们都添加给Car.

var Car = function(loc) { 
  var obj = {loc: loc}; 
  extend(obj, methods); 
  return obj;
};
var methods = { move : function(){ obj.loc++; }, 
on : function(){/*...*/}, 
off : function(){/*...*/}}

现在 向类Car添加新方法与修改一行代码一样简单,并且该方法会自动被添加到对象中。如果不熟悉extend,可以看一下众多开源库当中的实现。它不是JavaScript中自带的函数。现在看一下内存中的变化

我们把存储函数对象的变量move改成了存储对象的变量methods,其中的对象拥有一个属性move并指向同一个函数。

这样修改比较好,因为这个methods对象可以存储所有你想添加在每个Car实例的任何方法。这里有一个问题,methods现在的命名,无法明确表现出methods对象和Car之间的从属关系。很容易将其误认为是其他某个类的方法容器。此外,在当前作用域中,它是一个全局变量,而这是我们应该避免的。更好的方法是将methods对象明确地封装在类Car函数中,使之清晰明了的捆绑在一起。但是将类似methods的对象存储为另一对象的属性与将其存储在全局变量中一样容易,因此我们可以利用函数Car的对象属性,来存储这些方法。我们不再使用名为methods的全局变量,而是给函数Car赋予一个属性.methods

var Car = function(loc) { 
  var obj = {loc: loc}; 
  extend(obj, Car.methods); 
  return obj;
};
Car.methods = {
  move : function(){ this.loc++;
  }
};

很多人子昂这里比较迷惑,这是因为他们忘记了函数只是特殊的对象,除了可以被调用之外,函数还可以像其它对象一样存储属性。再看一下内存的情况,可以想象变量Car指代的函数对象,可能拥有一个.methods属性指向我们存储所有车辆方法的对象。

为了帮助理解,可以想象变量Car存储的是一个对象,而非一个函数。这样就可以很容易将.methods 属性想象为Car对象的属性,但是需要注意,函数和普通对象之间非常相似,因此函数也可以像其他对象一样拥有属性。同事也需要知道,在函数属性和调用函数时你期望发生的事之间,并没有交互作用。函数调用只导致函数体内的代码执行,调用函数并不会跟该函数属性发生任何交互。这只是一个简单的属性访问,如同平常一样,但此次目的是将这些对象方法从全局作用域中移出并整理,Car.methods属性并没有什么特殊,知识为了方便被存储为Car 的属性。
函数在JavaScript中非常重要,它是JavaScript类的核心。JavaScript类就是一个可以创建很多相似对象的函数。每当一个函数创建多个对象,而他们全都遵从大致相同的方法和属性模式,这个函数就可以被称为类,但是需要了解这个定义是有争议的。一些观点认为不应该将类归结为这么简单的定义,在其他语言中的确如此。但是在JavaScript中,我认为这个定义是对我们最有用的。如果我们希望再扩大类的定义,表示所有可以创建大量相似对象的构造器,我们需要检视每一个能想到的可以写出这样一个类的方法。
从上一篇的代码修饰器开始,再将其重构为函数类模式。接下来我会再写一下原型类和伪类模式,谈一下各种方式的优劣。

作者:长梦未央
图片来源:优达学城付费课程
个别文字摘自教学视频老师的讲解

相关文章

网友评论

    本文标题:一起来聊聊JavaScript核心中的"函数类&quo

    本文链接:https://www.haomeiwen.com/subject/npqspxtx.html