无需申请自动送彩金68_白菜送彩金网站大全[无需申请秒送彩金]
做最好的网站
来自 无需申请自动 2019-07-13 00:26 的文章
当前位置: 无需申请自动送彩金68 > 无需申请自动 > 正文

Javascript技术栈中的四种依赖注入小结,JS三级可

JS中的this关键字让很多新老JS开发人员都感到困惑。这篇文章将对this关键字进行完整地阐述。读完本文以后,您的困惑将全部消除。您将学会如何在各种不同的情形正确运用this。

本文实例讲述了JS三级可折叠菜单实现方法。分享给大家供大家参考,具体如下:

作为面向对象编程中实现控制反转(Inversion of Control,下文称IoC)最常见的技术手段之一,依赖注入(Dependency Injection,下文称DI)可谓在OOP编程中大行其道经久不衰。比如在J2EE中,就有大名鼎鼎的执牛耳者Spring。Javascript社区中自然也不乏一些积极的尝试,广为人知的AngularJS很大程度上就是基于DI实现的。遗憾的是,作为一款缺少反射机制、不支持Annotation语法的动态语言,Javascript长期以来都没有属于自己的Spring框架。当然,伴随着ECMAScript草案进入快速迭代期的春风,Javascript社区中的各种方言、框架可谓群雄并起,方兴未艾。可以预见到,优秀的JavascriptDI框架的出现只是早晚的事。

我们和在英语、法语这样的自然语言中使用名词一样地使用this。比如,“John飞快地跑着,因为他想追上火车”。请注意这句话中的代指John的代名词“他”。我们原本也可以这样表达,“John飞快地跑着,因为John想追上火车”。按照正常的语言习惯,我们并不按第二种方式表达。如果我们真按第二种方式说话,我们的家人和基友一定会把我们当成怪胎。说不定不止家人,甚至连我们的酒肉朋友和同事都会远离我们。类似地,在JS中,我们把this关键字当成一种快捷方式,或者说是引用(referent)。this关键字指向的是当前上下文(context,下文中将会对此作专门的解释)的主体(subject),或者当前正在被执行的代码块的主体。

.ASPX代码:

本文总结了Javascript中常见的依赖注入方式,并以inversify.js为例,介绍了方言社区对于Javascript中DI框架的尝试和初步成果。文章分为四节:

考虑以下代码:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="NavigateMenu.aspx.cs" Inherits="NavigateMenu" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
  <title>无标题页</title>
  <script type="text/javascript" src="JScript.js" ></script>
  <style type="text/css">
*{
 margin: 0px;
 padding: 0px;
 border:0px;
}
#nav{
 width: 600px;
 margin: 0px;
 padding: 0px;
 font-size: 14px;
 line-height: 30px;
}
#nav li{
 width: 180px;
 padding-left: 20px;
 padding-bottom: 1px;
 list-style-image: none;
 list-style-type: none;
 background-color: #FFFFFF;
}
#nav a{
 padding-left: 20px;
 background-color: #BFBFBF;
 display: block;
 text-decoration: none;
}
#nav a:hover {
 background-color: #FF9175;
}
#nav li ul{
 padding-top: 1px;
 list-style-image: none;
 list-style-type: none;
}
#nav li li{
 padding-left: 0px;
}
#nav ul a{
 background-color: #EBEBEB;
}
.cx {
 display:none;
 visibility:hidden;
}
.ex {
 display:inherit;
 visibility:inherit;
}
</style>
<script type="text/javascript" >
 window.onload=function(){
 statUp();
 }
</script>
</head>
<body>
<form id="form1" runat="server">
<div id="Parent">
<ul id="nav">
<li><a href="javascript:void(0);" onclick="doMenu(this,'1')">菜单1</a>
 <ul>
 <li><a href="javascript:void(0);">菜单1_1</a></li>
 <li><a href="javascript:void(0);">菜单1_2</a></li>
 </ul>
</li>
<li><a href="javascript:void(0);" onclick="doMenu(this,'1')">菜单2</a>
 <ul>
 <li><a href="javascript:void(0);" onclick="doMenu(this,'2')" >菜单2_1</a>
 <ul>
 <li>菜单2_1_1</li>
 <li>菜单2_1_2</li>
 </ul>
 </li>
 <li><a href="javascript:void(0);" onclick="doMenu(this,'2')">菜单2_2</a>
  <ul>
 <li>菜单2_2_1</li>
 </ul>
 </li>
 </ul>
</li>
</ul>
</div>
</form>
</body>
</html>

一. 基于Injector、Cache和函数参数名的依赖注入
二. AngularJS中基于双Injector的依赖注入
三. TypeScript中基于装饰器和反射的依赖注入
四. inversify.js——Javascript技术栈中的IoC容器

var person = {
   firstName: "Penelope",
   lastName: "Barrymore",
   fullName: function () {
   // 正如我们在文中提到的使用“他”作为代名词一样,我们在这里使用this

   console.log(this.firstName   " "   this.lastName);
   //我们其实也可以这样写:
   console.log(person.firstName   " "   person.lastName);
   }
  }

js文件代码:

一. 基于Injector、Cache和函数参数名的依赖注入

如果我们使用person.firstName和person.lastName,某些情况下代码会变得模棱两可。例如,全局变量中有一个跟person同名的变量名。这种情况下,如果我们想要读取person.firstName的值,系统将有可能从全局变量的person变量中读取firstName属性(property)。这样一来,我们调试代码的时候很难发现错误。所以this并不只起到美化代码的作用,同时也是为了保证程序的准确性。这种做法实际上和前面讲到的“他”的用法一样,使得我们的代码更加清晰。“他”所引用的正是句首的“John”。

function doMenu(obj,strDeep){
 var items=obj.parentNode.getElementsByTagName("ul");
 //获取a 对象你节点li 下包含的 所有ul集合
 var itmUl;
 var deeps=strDeep; //strDeep 为当前菜单的级数
 if(items.length>0){
 itmUl=items[0];
 alert(itmUl);
 }
 if(itmUl.className!="ex"){
 cxAll();//当前节点为关闭状态时,先执行关闭所有ul子菜单
 if(deeps=='2'){ //若要展开三级菜单当,还要将其二级父菜单展开
 itmUl.parentNode.parentNode.className="ex";
 }
 itmUl.className="ex"; //展开下级菜单
 }else{
 itmUl.className="cx";
 }
}
function statUp(){
 cxAll();
 var ulDom=document.getElementById("nav");
 var items=ulDom.getElementsByTagName("ul");
}
function cxAll(){
 var ulDom=document.getElementById("nav");
 var items=ulDom.getElementsByTagName("ul");
 for (var i=0;i<items.length;i  )
 {
 items[i].className="cx";
 }
}

尽管Javascript中不原生支持反射(Reflection)语法,但是Function.prototype上的toString方法却为我们另辟蹊径,使得在运行时窥探某个函数的内部构造成为可能:toString方法会以字符串的形式返回包含function关键字在内的整个函数定义。从这个完整的函数定义出发,我们可以利用正则表达式提取出该函数所需要的参数,从而在某种程度上得知该函数的运行依赖。
比如Student类上write方法的函数签名write(notebook, pencil)就说明它的执行依赖于notebook和pencil对象。因此,我们可以首先把notebook和pencil对象存放到某个cache中,再通过injector(注入器、注射器)向write方法提供它所需要的依赖:

正如代名词“他”被用来代指句中的先行词(先行词就是代名词所指示的名词),this关键字以同样的方式来引用当前方法(function,也可以称为函数)所绑定(bound)的对象。this不只引用对象,同时包含了对象的值。跟先行词一样,this也可以被理解成在上下文中用来引用当前对象(又叫作“先行词对象”)的快捷方式(或者来适度减少歧义的替代品)。我们迟些会专门讲解“上下文”。

更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《JavaScript查找算法技巧总结》、《JavaScript动画特效与技巧汇总》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》

var cache = {};
// 通过解析Function.prototype.toString()取得参数名
function getParamNames(func) {
  // 正则表达式出自http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript
  var paramNames = func.toString().match(/^functions*[^(]*(s*([^)]*))/m)[1];
  paramNames = paramNames.replace(/ /g, '');
  paramNames = paramNames.split(',');
  return paramNames;
}
var injector = {
  // 将func作用域中的this关键字绑定到bind对象上,bind对象可以为空
  resolve: function (func, bind) {
    // 取得参数名
    var paramNames = getParamNames(func);
    var params = [];
    for (var i = 0; i < paramNames.length; i  ) {
      // 通过参数名在cache中取出相应的依赖
      params.push(cache[paramNames[i]]);
    }
    // 注入依赖并执行函数
    func.apply(bind, params);
  }
};

function Notebook() {}
Notebook.prototype.printName = function () {
  console.log('this is a notebook');
};

function Pencil() {}
Pencil.prototype.printName = function () {
  console.log('this is a pencil');
};

function Student() {}
Student.prototype.write = function (notebook, pencil) {
  if (!notebook || !pencil) {
    throw new Error('Dependencies not provided!');
  }
  console.log('writing...');
};
// 提供notebook依赖
cache['notebook'] = new Notebook();
// 提供pencil依赖
cache['pencil'] = new Pencil();
var student = new Student();
injector.resolve(student.write, student); // writing...

this关键字基本理论

希望本文所述对大家JavaScript程序设计有所帮助。

有时候为了保证良好的封装性,也不一定要把cache对象暴露给外界作用域,更多的时候是以闭包变量或者私有属性的形式存在的:

首先我们得知道,对象(Object)有属性集(properties),所有的方法(function)也有属性集。运行到某个方法的时候就有了一个this属性—一个存储了调用该方法(准确地说是使用了this关键字的方法)的对象的值的变量。

您可能感兴趣的文章:

  • Vue.js组件tree实现无限级树形菜单
  • JS中用三种方式实现导航菜单中的二级下拉菜单
  • javascript鼠标滑过显示二级菜单特效
  • 全国省市二级联动下拉菜单 js版
  • 最简单js代码实现select二级联动下拉菜单
  • 基于Javascript实现二级联动菜单效果
  • 原生JS实现N级菜单的代码
function Injector() {
  this._cache = {};
}

Injector.prototype.put = function (name, obj) {
  this._cache[name] = obj;
};

Injector.prototype.getParamNames = function (func) {
  // 正则表达式出自http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript
  var paramNames = func.toString().match(/^functions*[^(]*(s*([^)]*))/m)[1];
  paramNames = paramNames.replace(/ /g, '');
  paramNames = paramNames.split(',');
  return paramNames;
};

Injector.prototype.resolve = function (func, bind) {
  var self = this;
  var paramNames = self.getParamNames(func);
  var params = paramNames.map(function (name) {
    return self._cache[name];
  });
  func.apply(bind, params);
};

var injector = new Injector();

var student = new Student();
injector.put('notebook', new Notebook());
injector.put('pencil', new Pencil())
injector.resolve(student.write, student); // writing...

this关键字始终指向一个对象并持有这个对象的值,尽管它可以出现在全局范围(global scope)方法(函数)以外的地方,但它通常出现在方法体中。值得注意的是,如果我们使用严格模式(strict mode),并在全局方法(global functions)或者没有绑定到任何对象的匿名方法中使用this关键字时,this将会指向undefined。

比如现在要执行Student类上的另一个方法function draw(notebook, pencil, eraser),因为injector的cache中已经有了notebook和pencil对象,我们只需要将额外的eraser也存放到cache中:

this被用在方法体中,比如方法A,它将指向调用方法A的对象的值。并不是任何情况我们都能找到调用方法A的对象名,这时候就用this来访问调用方法A的对象所拥有的方法和属性。this确实只是一个用来引用先行词—调用方法的对象—的快捷方式。

function Eraser() {}
Eraser.prototype.printName = function () {
  console.log('this is an eraser');
};

// 为Student增加draw方法
Student.prototype.draw = function (notebook, pencil, eraser) {
  if (!notebook || !pencil || !eraser) {
    throw new Error('Dependencies not provided!');
  }
  console.log('drawing...');
};

injector.put('eraser', new Eraser());
injector.resolve(student.draw, student);

我们来仔细体会下面这段使用this的代码。

通过依赖注入,函数的执行和其所依赖对象的创建逻辑就被解耦开来了。
当然,随着grunt/gulp/fis等前端工程化工具的普及,越来越多的项目在上线之前都经过了代码混淆(uglify),因而通过参数名去判断依赖并不总是可靠,有时候也会通过为function添加额外属性的方式来明确地说明其依赖:

var person = {
  firstName: "Penelope",
  lastName: "Barrymore",
  //this用在showFullName方法中,而showFullName定义在person对象中,由于调用showFullName的是person这个对象,所以this拥有person的值

  showFullName: function() {
    console.log(this.firstName   " "   this.lastName);
  }
}
person.showFullName(); // 结果:Penelope Barrymore
Student.prototype.write.depends = ['notebook', 'pencil'];
Student.prototype.draw.depends = ['notebook', 'pencil', 'eraser'];
Injector.prototype.resolve = function (func, bind) {
  var self = this;
  // 首先检查func上是否有depends属性,如果没有,再用正则表达式解析
  func.depends = func.depends || self.getParamNames(func);
  var params = func.depends.map(function (name) {
    return self._cache[name];
  });
  func.apply(bind, params);
};
var student = new Student();
injector.resolve(student.write, student); // writing...
injector.resolve(student.draw, student); // draw...

再考虑下面这段使用了this的jQuery示例。

二. AngularJS中基于双Injector的依赖注入

//这是一段很简单的jQuery代码

$("button").click(function(event) {
  // $(this) 会指向$("button")对象
  // 因为$("button")对象调用click方法
  console.log($(this).prop("name"));
});

熟悉AngularJS的同学很快就能联想到,在injector注入之前,我们在定义module时还可以调用config方法来配置随后会被注入的对象。典型的例子就是在使用路由时对$routeProvider的配置。也就是说,不同于上一小节中直接将现成对象(比如new Notebook())存入cache的做法,AngularJS中的依赖注入应该还有一个”实例化”或者”调用工厂方法”的过程。
这就是providerInjector、instanceInjector以及他们各自所拥有的providerCache和instanceCache的由来。
在AngularJS中,我们能够通过依赖注入获取到的injector通常是instanceInjector,而providerInjector则是以闭包中变量的形式存在的。每当我们需要AngularJS提供依赖注入服务时,比如想要获取notebook,instanceInjector会首先查询instanceCache上是存在notebook属性,如果存在,则直接注入;如果不存在,则将这个任务转交给providerInjector;providerInjector会将”Provider”字符串拼接到”notebook”字符串的后面,组成一个新的键名”notebookProvider”,再到providerCache中查询是否有notebookProvider这个属性,如有没有,则抛出异常Unknown Provider异常:

我想详细地说一下上面这个jQuery示例:$(this)的使用,这是this的jQuery版本,它用于匿名方法中,这个匿名方法在button的单击事件里执行。这里之所以说$(this)被绑定到button对象,是因为jQuery库把$(this)绑定到调用click方法的对象上。因此,尽管$(this)被定义在一个自身无法访问“自身”变量的匿名方法里,$(this)仍会指向button对象。

如果有,则将这个provider返回给instanceInjector;instanceInjector拿到notebookProvider后,会调用notebookProvider上的工厂方法$get,获取返回值notebook对象,将该对象放到instanceCache中以备将来使用,同时也注入到一开始声明这个依赖的函数中。

请注意,button是一个HTML页面的DOM元素,它同时是一个对象:在上面的例子中,因为我们把它包装在了jQuery的$()方法里,所以它是一个jQuery对象。

需要注意的是,AngularJS中的依赖注入方式也是有缺陷的:利用一个instanceInjector单例服务全局的副作用就是无法单独跟踪和控制某一条依赖链条,即使在没有交叉依赖的情况下,不同module中的同名provider也会产生覆盖,这里就不详细展开了。

this关键字的核心

另外,对于习惯于Java和C#等语言中高级IoC容器的同学来说,看到这里可能觉得有些别扭,毕竟在OOP中,我们通常不会将依赖以参数的形式传递给方法,而是作为属性通过constructor或者setters传递给实例,以实现封装。的确如此,一、二节中的依赖注入方式没有体现出足够的面向对象特性,毕竟这种方式在Javascript已经存在多年了,甚至都不需要ES5的语法支持。希望了解Javascript社区中最近一两年关于依赖注入的研究和成果的同学,可以继续往下阅读。

下面这条规则可以帮助你彻底搞懂this关键字:如果一个方法内部使用了this关键字,仅当对象调用该方法时this关键字才会被赋值。我们估且把使用了this关键字的方法称为“this方法”。

三. TypeScript中基于装饰器和反射的依赖注入

尽管看上去this引用了它在代码中所存在于的对向,事实上在方法被调用之前它并没有被赋值,而赋给它的值又严格地依赖于实际调用“this方法”的对象。this通常会被赋予调用对象的值,下面有一些特殊情况。

博主本身对于Javascript的各种方言的学习并不是特别热情,尤其是现在EMCAScript提案、草案更新很快,很多时候借助于polyfill和babel的各种preset就能满足需求了。但是TypeScript是一个例外(当然现在Decorator也已经是提案了,虽然阶段还比较早,但是确实已经有polyfill可以使用)。上文提到,Javascript社区中迟迟没有出现一款优秀的IoC容器和自身的语言特性有关,那就依赖注入这个话题而言,TypeScript给我们带来了什么不同呢?至少有下面这几点:
* TypeScript增加了编译时类型检查,使Javascript具备了一定的静态语言特性
* TypeScript支持装饰器(Decorator)语法,和传统的注解(Annotation)颇为相似
* TypeScript支持元信息(Metadata)反射,不再需要调用Function.prototype.toString方法
下面我们就尝试利用TypeScript带来的新语法来规范和简化依赖注入。这次我们不再向函数或方法中注入依赖了,而是向类的构造函数中注入。
TypeScript支持对类、方法、属性和函数参数进行装饰,这里需要用到的是对类的装饰。继续上面小节中用到的例子,利用TypeScript对代码进行一些重构:

全局范围里的this

class Pencil {
  public printName() {
    console.log('this is a pencil');
  }
}

class Eraser {
  public printName() {
    console.log('this is an eraser');
  }
}

class Notebook {
  public printName() {
    console.log('this is a notebook');
  }
}

class Student {
  pencil: Pencil;
  eraser: Eraser;
  notebook: Notebook;
  public constructor(notebook: Notebook, pencil: Pencil, eraser: Eraser) {
    this.notebook = notebook;
    this.pencil = pencil;
    this.eraser = eraser;
  }
  public write() {
    if (!this.notebook || !this.pencil) {
      throw new Error('Dependencies not provided!');
    }
    console.log('writing...');
  }
  public draw() {
    if (!this.notebook || !this.pencil || !this.eraser) {
      throw new Error('Dependencies not provided!');
    }
    console.log('drawing...');
  }
}

在全局域中,代码在浏览器里执行,所有变量和方法都属于window对象。因而当我们在全局域中使用this关键字的时候,它会被指向(并拥有)全局变量window对象。如前所述,严格模式除外。window对象是JS一个程序或一张网页的主容器。

下面是injector和装饰器Inject的实现。injector的resolve方法在接收到传入的构造函数时,会通过name属性取出该构造函数的名字,比如class Student,它的name属性就是字符串”Student”。再将Student作为key,到dependenciesMap中去取出Student的依赖,至于dependenciesMap中是何时存入的依赖关系,这是装饰器Inject的逻辑,后面会谈到。Student的依赖取出后,由于这些依赖已经是构造函数的引用而非简单的字符串了(比如Notebook、Pencil的构造函数),因此直接使用new语句即可获取这些对象。获取到Student类所依赖的对象之后,如何把这些依赖作为构造函数的参数传入到Student中呢?最简单的莫过于ES6的spread操作符。在不能使用ES6的环境下,我们也可以通过伪造一个构造函数来完成上述逻辑。注意为了使instanceof操作符不失效,这个伪造的构造函数的prototype属性应该指向原构造函数的prototype属性。

因而:

var dependenciesMap = {};
var injector = {
  resolve: function (constructor) {
    var dependencies = dependenciesMap[constructor.name];
    dependencies = dependencies.map(function (dependency) {
      return new dependency();
    });
    // 如果可以使用ES6的语法,下面的代码可以合并为一行:
    // return new constructor(...dependencies);
    var mockConstructor: any = function () {
      constructor.apply(this, dependencies);
    };
    mockConstructor.prototype = constructor.prototype;
    return new mockConstructor();
  }
};
function Inject(...dependencies) {
  return function (constructor) {
    dependenciesMap[constructor.name] = dependencies;
    return constructor;
  };
}
var firstName = "Peter",
lastName = "Ally";

function showFullName() {
  //在这个方法中,this将指向window对象。因为showFullName()出现在全局域里。

  console.log(this.firstName   " "   this.lastName);
}

var person = {
  firstName: "Penelope",
  lastName: "Barrymore",
  showFullName: function() {
    //下面这行代码,this指向person对象,因为showFullName方法会被person对象调用。
    console.log(this.firstName   " "   this.lastName);
  }
}

showFullName(); // Peter Ally

//window对象包含了所有的全局变量和方法,因而会有以下输出
window.showFullName(); // Peter Ally

//使用了this关键字的showFullName方法定义在person对象里,this关键字指向person对象,因以会有以下输出
person.showFullName(); // Penelope Barrymore

injector和装饰器Inject的逻辑完成后,就可以用来装饰class Student并享受依赖注入带来的乐趣了:

对付this有绝招

// 装饰器的使用非常简单,只需要在类定义的上方添加一行代码
// Inject是装饰器的名字,后面是function Inject的参数
@Inject(Notebook, Pencil, Eraser)
class Student {
  pencil: Pencil;
  eraser: Eraser;
  notebook: Notebook;
  public constructor(notebook: Notebook, pencil: Pencil, eraser: Eraser) {
    this.notebook = notebook;
    this.pencil = pencil;
    this.eraser = eraser;
  }
  public write() {
    if (!this.notebook || !this.pencil) {
      throw new Error('Dependencies not provided!');
    }
    console.log('writing...');
  }
  public draw() {
    if (!this.notebook || !this.pencil || !this.eraser) {
      throw new Error('Dependencies not provided!');
    }
    console.log('drawing...');
  }
}
var student = injector.resolve(Student);
console.log(student instanceof Student); // true
student.notebook.printName(); // this is a notebook
student.pencil.printName(); // this is a pencil
student.eraser.printName(); // this is an eraser
student.draw(); // drawing
student.write(); // writing

本文由无需申请自动送彩金68发布于无需申请自动,转载请注明出处:Javascript技术栈中的四种依赖注入小结,JS三级可

关键词: