bt365体育投注.主頁欢迎您!!

    <acronym id="zvmrr"></acronym>
    <td id="zvmrr"></td>
  • <tr id="zvmrr"><label id="zvmrr"></label></tr>
  • <acronym id="zvmrr"></acronym>
  • Reco

    Reco 查看完整档案

    北京编辑  |  填写毕业院校  |  填写所在公司/组织 no 编辑
    编辑

    敢作敢为

    个人动态

    Reco 收藏了文章 · 11月4日

    JavaScript代理的惊人威力

    今天我们要学习的是ECMAScript 6的代理。我们将在本文中涉及以下主题。

    • 什么是代理
    • 代理人在行动
    • 谁使用代理
    • 使用案例和实例
    • 资源

    让我们开始吧:)

    什么是代理

    MDN网站所述。

    Proxy对象使你能够为另一个对象创建一个代理,它可以拦截和重新定义该对象的基本操作。

    他们在解释什么是代理的时候,说它可以创建一个代理**,这有点搞笑。当然,他们并没有错,但我们可以简化这个说法,使其更加友好,说

    _Proxy_对象可以让你包裹目标对象,通过这样做,我们可以拦截和重新定义该对象的基本操作。

    基本上,它的意思是我们要把一个对象,用Proxy包裹起来,这将允许我们创建一个 "隐藏 "的门,并控制所有对所需对象的访问。

    一个小插曲,Proxy也是一种软件设计模式,你一定要阅读一下(维基百科链接)。

    const target = {
      message1: "hello",
      message2: "everyone"
    };
    
    const handler = {};
    
    const proxy = new Proxy(target, handler);
    

    一个 "代理 "是用两个参数创建的。

    • "target":你要包裹的原始对象(代理)。
    • handler:定义哪些操作将被拦截,以及如何重新定义被拦截的操作的对象,也可以调用 "陷阱"。

    大多数浏览器都支持代理,但也有一些老的浏览器不支持(当然是IE),你可以查看完整的列表这里。google有一个polyfill的Proxies,但它不支持所有的Proxy功能。

    现在我们知道了什么是代理,我们想看看我们能用它做什么。

    代理人在行动

    让我们想象一下,我们是A Bank。我们想知道每次银行账户余额被访问并被通知上。我们将使用最简单的handler操作/陷阱。get

    在上面的例子中,我们有一个银行账户对象,里面有我的名字和2020的余额。

    const bankAccount = {
      balance: 2020,
      name: 'Georgy Glezer'
    };
    
    const handler = {
       get: function(target, prop, receiver) {
        if (prop === 'balance') {
        console.log(`Current Balance Of: ${target.name} Is: ${target.balance} `);
        }
    
        return target[prop];
      }
    };
    
    const wrappedBankAcount = new Proxy(bankAccount, handler);
    
    wrappedBankAcount.balance; // access to the balance
    
    // OUTPUT:
    // Current Balance Of: Georgy Glezer Is: 2020
    // 2020
    

    这次的处理者对象实现的是get操作/陷阱,它接收一个有3个参数的函数和get的返回值。

    • target。被访问的对象(我们封装的对象)。
    • prop: 被访问的属性。在我们的例子中被访问的属性--这里是 "balance"。
    • receiver:接受者。可以是代理,也可以是继承自代理的对象。

    我们定义了一个条件,如果被访问的属性是"_balance"_,我们将通知(log)余额和当前用户名,并返回属性_"balance"_。

    从输出中可以看到,一旦 "余额 "属性被访问,我们通过使用代理和设置get操作/陷阱,很容易就通知(log)了这次访问。

    我们继续用我们Bank的想法,要求每次有人从银行账户中取钱,我们都要通知一下。而另一个约束条件是,银行不允许出现负余额。为了达到这个目的,我们这次要使用set处理程序/陷阱。

    在上面的例子中,我们通知当前的余额和取款后的新余额,如果新的余额为负数,我们也会通知并中止取款操作。

    const bankAccount = {
        balance: 2020,
        name: 'Georgy Glezer'
    };
    
    const handler = {
        set: function (obj, prop, value) {
            console.log(`Current Balance: ${obj.balance}, New Balance: ${value}`);
    
            if (value < 0) {
                console.log(`We don't allow Negative Balance!`);
                return false;
            }
            obj[prop] = value;
    
            return true;
        }
    };
    
    const wrappedBankAcount = new Proxy(bankAccount, handler);
    
    wrappedBankAcount.balance -= 2000; // access to the balance
    console.log(wrappedBankAcount.balance);
    
    wrappedBankAcount.balance -= 50; // access to the balance
    console.log(wrappedBankAcount.balance);
    
    // OUTPUT:
    // Current Balance: 2020, New Balance: 20
    // 20
    // Current Balance: 20, New Balance: -30
    // We don't allow Negative Balance!
    // 20
    

    我们使用的是set operator/trap,它是一个返回布尔值(true/false)的函数,用来判断更新操作是否成功。它接收以下参数。

    • target: 被访问的对象(我们封装的对象)。
    • prop:正在访问的对象(我们封装的对象)。在我们的例子中,被访问的属性是 "balance"。
    • value。应该更新的新值。
    • receiver*: 原先被分配到的对象。这通常是代理本身。但set()处理程序也可以通过原型链或其他各种方式间接调用。

    你可以看到,它其实和get很相似,但只是多接收了1个新值的参数。

    这2个操作符/陷阱是最常见的,如果你有兴趣找到所有现有的操作符/陷阱,你可以查看这里

    谁使用代理

    许多流行的库都使用了这种技术,例如: * [MobX]()

    还有更多......他们中的大多数人都利用了Proxies给我们带来的惊人力量,并为我们提供了很棒的库。

    使用案例和示例

    我们已经看到,我们可以使用代理服务器来进行。

    • 记录(通知银行)
    • 验证(阻止负数余额更新)

    缓存

    我们将再次使用get操作符/陷阱,并将 "dollars "属性添加到我们的对象中。在每次访问 "dollars "属性时,我们将计算我们的余额价值多少美元。因为计算是一个繁重的操作,所以我们希望尽可能的Cache它。

    const bankAccount = {
        balance: 10,
        name: 'Georgy Glezer',
        get dollars() {
            console.log('Calculating Dollars');
            return this.balance *3.43008459;
        }
    };
    
    let cache = {
        currentBalance: null,
        currentValue: null
    };
    
    const handler = {
        get: function (obj, prop) {
            if (prop === 'dollars') {
                let value = cache.currentBalance !== obj.balance ? obj[prop] : cache.currentValue;
    
                cache.currentValue = value;
                cache.currentBalance = obj.balance;
    
                return value;
            }
    
            return obj[prop];
        }
    };
    
    const wrappedBankAcount = new Proxy(bankAccount, handler);
    
    console.log(wrappedBankAcount.dollars);
    console.log(wrappedBankAcount.dollars);
    console.log(wrappedBankAcount.dollars);
    console.log(wrappedBankAcount.dollars);
    
    // OUTPUT:
    // Calculating Dollars
    // 34.3008459
    // 34.3008459
    // 34.3008459
    // 34.3008459
    

    正如你在例子中所看到的,我们有一个缓存对象,它保存着当前的银行余额和以美元为单位的余额价值。每次有人访问"_dollars"_属性时,我们都会先进行计算,然后将其缓存起来。

    DOM操作

    我们想在每次余额发生变化时更新屏幕上的文字。我们将使用一个set操作符/陷阱,在每次改变数值时,我们将更新屏幕上的DOM元素。

    const bankAccount = {
      balance: 2020,
      name: "Georgy Glezer",
      get text() {
        return `${this.name} Balance Is: ${this.balance}`;
      }
    };
    
    const objectWithDom = (object, domId) => {
      const handler = {
        set: function (obj, prop, value) {
          obj[prop] = value;
    
          document.getElementById(domId).innerHTML = obj.text;
    
          return true;
        }
      };
    
      return new Proxy(object, handler);
    };
    
    // create a dom element with id: bank-account
    const wrappedBankAccount = objectWithDom(bankAccount, "bank-account");
    
    wrappedBankAccount.balance = 26;
    wrappedBankAccount.balance = 100000;
    

    在这里,我们创建了一个辅助函数,这样我们就可以存储DOM元素的ID,并在set operator/trap中添加了简单的行来更新DOM元素。很简单,对吧?让我们看看结果:)

    Image for post

    概要

    综上所述,我们了解了ECMAScript 6 Proxies,我们如何使用它们,以及用于什么目的。在我看来,Proxies是一个很神奇的工具,你可以用它来做各种各样的选择,你只需要想想什么最适合你:) 。

    如果您喜欢这篇文章,欢迎关注并拍手??。

    资源简介

    查看原文

    Reco 发布了文章 · 11月4日

    JavaScript代理的惊人威力

    今天我们要学习的是ECMAScript 6的代理。我们将在本文中涉及以下主题。

    • 什么是代理
    • 代理人在行动
    • 谁使用代理
    • 使用案例和实例
    • 资源

    让我们开始吧:)

    什么是代理

    MDN网站所述。

    Proxy对象使你能够为另一个对象创建一个代理,它可以拦截和重新定义该对象的基本操作。

    他们在解释什么是代理的时候,说它可以创建一个代理**,这有点搞笑。当然,他们并没有错,但我们可以简化这个说法,使其更加友好,说

    _Proxy_对象可以让你包裹目标对象,通过这样做,我们可以拦截和重新定义该对象的基本操作。

    基本上,它的意思是我们要把一个对象,用Proxy包裹起来,这将允许我们创建一个 "隐藏 "的门,并控制所有对所需对象的访问。

    一个小插曲,Proxy也是一种软件设计模式,你一定要阅读一下(维基百科链接)。

    const target = {
      message1: "hello",
      message2: "everyone"
    };
    
    const handler = {};
    
    const proxy = new Proxy(target, handler);
    

    一个 "代理 "是用两个参数创建的。

    • "target":你要包裹的原始对象(代理)。
    • handler:定义哪些操作将被拦截,以及如何重新定义被拦截的操作的对象,也可以调用 "陷阱"。

    大多数浏览器都支持代理,但也有一些老的浏览器不支持(当然是IE),你可以查看完整的列表这里。google有一个polyfill的Proxies,但它不支持所有的Proxy功能。

    现在我们知道了什么是代理,我们想看看我们能用它做什么。

    代理人在行动

    让我们想象一下,我们是A Bank。我们想知道每次银行账户余额被访问并被通知上。我们将使用最简单的handler操作/陷阱。get

    在上面的例子中,我们有一个银行账户对象,里面有我的名字和2020的余额。

    const bankAccount = {
      balance: 2020,
      name: 'Georgy Glezer'
    };
    
    const handler = {
       get: function(target, prop, receiver) {
        if (prop === 'balance') {
        console.log(`Current Balance Of: ${target.name} Is: ${target.balance} `);
        }
    
        return target[prop];
      }
    };
    
    const wrappedBankAcount = new Proxy(bankAccount, handler);
    
    wrappedBankAcount.balance; // access to the balance
    
    // OUTPUT:
    // Current Balance Of: Georgy Glezer Is: 2020
    // 2020
    

    这次的处理者对象实现的是get操作/陷阱,它接收一个有3个参数的函数和get的返回值。

    • target。被访问的对象(我们封装的对象)。
    • prop: 被访问的属性。在我们的例子中被访问的属性--这里是 "balance"。
    • receiver:接受者。可以是代理,也可以是继承自代理的对象。

    我们定义了一个条件,如果被访问的属性是"_balance"_,我们将通知(log)余额和当前用户名,并返回属性_"balance"_。

    从输出中可以看到,一旦 "余额 "属性被访问,我们通过使用代理和设置get操作/陷阱,很容易就通知(log)了这次访问。

    我们继续用我们Bank的想法,要求每次有人从银行账户中取钱,我们都要通知一下。而另一个约束条件是,银行不允许出现负余额。为了达到这个目的,我们这次要使用set处理程序/陷阱。

    在上面的例子中,我们通知当前的余额和取款后的新余额,如果新的余额为负数,我们也会通知并中止取款操作。

    const bankAccount = {
        balance: 2020,
        name: 'Georgy Glezer'
    };
    
    const handler = {
        set: function (obj, prop, value) {
            console.log(`Current Balance: ${obj.balance}, New Balance: ${value}`);
    
            if (value < 0) {
                console.log(`We don't allow Negative Balance!`);
                return false;
            }
            obj[prop] = value;
    
            return true;
        }
    };
    
    const wrappedBankAcount = new Proxy(bankAccount, handler);
    
    wrappedBankAcount.balance -= 2000; // access to the balance
    console.log(wrappedBankAcount.balance);
    
    wrappedBankAcount.balance -= 50; // access to the balance
    console.log(wrappedBankAcount.balance);
    
    // OUTPUT:
    // Current Balance: 2020, New Balance: 20
    // 20
    // Current Balance: 20, New Balance: -30
    // We don't allow Negative Balance!
    // 20
    

    我们使用的是set operator/trap,它是一个返回布尔值(true/false)的函数,用来判断更新操作是否成功。它接收以下参数。

    • target: 被访问的对象(我们封装的对象)。
    • prop:正在访问的对象(我们封装的对象)。在我们的例子中,被访问的属性是 "balance"。
    • value。应该更新的新值。
    • receiver*: 原先被分配到的对象。这通常是代理本身。但set()处理程序也可以通过原型链或其他各种方式间接调用。

    你可以看到,它其实和get很相似,但只是多接收了1个新值的参数。

    这2个操作符/陷阱是最常见的,如果你有兴趣找到所有现有的操作符/陷阱,你可以查看这里

    谁使用代理

    许多流行的库都使用了这种技术,例如: * [MobX]()

    还有更多......他们中的大多数人都利用了Proxies给我们带来的惊人力量,并为我们提供了很棒的库。

    使用案例和示例

    我们已经看到,我们可以使用代理服务器来进行。

    • 记录(通知银行)
    • 验证(阻止负数余额更新)

    缓存

    我们将再次使用get操作符/陷阱,并将 "dollars "属性添加到我们的对象中。在每次访问 "dollars "属性时,我们将计算我们的余额价值多少美元。因为计算是一个繁重的操作,所以我们希望尽可能的Cache它。

    const bankAccount = {
        balance: 10,
        name: 'Georgy Glezer',
        get dollars() {
            console.log('Calculating Dollars');
            return this.balance *3.43008459;
        }
    };
    
    let cache = {
        currentBalance: null,
        currentValue: null
    };
    
    const handler = {
        get: function (obj, prop) {
            if (prop === 'dollars') {
                let value = cache.currentBalance !== obj.balance ? obj[prop] : cache.currentValue;
    
                cache.currentValue = value;
                cache.currentBalance = obj.balance;
    
                return value;
            }
    
            return obj[prop];
        }
    };
    
    const wrappedBankAcount = new Proxy(bankAccount, handler);
    
    console.log(wrappedBankAcount.dollars);
    console.log(wrappedBankAcount.dollars);
    console.log(wrappedBankAcount.dollars);
    console.log(wrappedBankAcount.dollars);
    
    // OUTPUT:
    // Calculating Dollars
    // 34.3008459
    // 34.3008459
    // 34.3008459
    // 34.3008459
    

    正如你在例子中所看到的,我们有一个缓存对象,它保存着当前的银行余额和以美元为单位的余额价值。每次有人访问"_dollars"_属性时,我们都会先进行计算,然后将其缓存起来。

    DOM操作

    我们想在每次余额发生变化时更新屏幕上的文字。我们将使用一个set操作符/陷阱,在每次改变数值时,我们将更新屏幕上的DOM元素。

    const bankAccount = {
      balance: 2020,
      name: "Georgy Glezer",
      get text() {
        return `${this.name} Balance Is: ${this.balance}`;
      }
    };
    
    const objectWithDom = (object, domId) => {
      const handler = {
        set: function (obj, prop, value) {
          obj[prop] = value;
    
          document.getElementById(domId).innerHTML = obj.text;
    
          return true;
        }
      };
    
      return new Proxy(object, handler);
    };
    
    // create a dom element with id: bank-account
    const wrappedBankAccount = objectWithDom(bankAccount, "bank-account");
    
    wrappedBankAccount.balance = 26;
    wrappedBankAccount.balance = 100000;
    

    在这里,我们创建了一个辅助函数,这样我们就可以存储DOM元素的ID,并在set operator/trap中添加了简单的行来更新DOM元素。很简单,对吧?让我们看看结果:)

    Image for post

    概要

    综上所述,我们了解了ECMAScript 6 Proxies,我们如何使用它们,以及用于什么目的。在我看来,Proxies是一个很神奇的工具,你可以用它来做各种各样的选择,你只需要想想什么最适合你:) 。

    如果您喜欢这篇文章,欢迎关注并拍手??。

    资源简介

    查看原文

    赞 11 收藏 7 评论 0

    Reco 发布了文章 · 10月29日

    不需要框架的客户端JavaScript数据绑定

    最近我一直在思考纯JavaScript的功能。这是一门在过去几年里有显著发展的语言。许多流行的库(如模块加载器)和框架(如Angular,Vue.js和React)被创建,以解决原始的、过时的实现中存在的缺陷和差距。随着ECMAScript 6 / 2015,我相信这些限制大部分已经消失了。许多重要的功能都是开箱即用的,例如:

    最新的JavaScript版本没有完全支持的一个功能是_databinding_。但是实现它有多难呢?如果你使用重型框架的唯一动机是支持数据绑定,你可能会感到惊讶!

    Databinding example

    观察变化

    首先需要的是观察变化的能力。这很容易通过一个Observable类来实现。这个类需要做三件事。

    1. 跟踪一个值
    2. 允许听众订阅更改
    3. 当值发生变化时通知监听者

    下面是一个简单的实现。

    class Observable {
    
      constructor(value) {
        this._listeners = [];
        this._value = value;
      }
    
      notify() {
        this._listeners.forEach(listener => listener(this._value));
      }
    
      subscribe(listener) {
        this._listeners.push(listener);
      }
    
      get value() {
        return this._value;
      }
    
      set value(val) {
        if (val !== this._value) {
          this._value = val;
          this.notify();
        }
      }
    } 

    这个简单的类,利用内置的类suport(不需要TypeScript!)很好地处理了一切。这里是我们的新类的一个使用实例,它创建了一个可观察的类,监听变化,并将其记录到控制台。

    const name = new Observable("Jeremy");
    name.subscribe((newVal) => console.log(`Name changed to ${newVal}`));
    name.value = "Doreen";
    // logs "Name changed to Doreen" to the console 

    这很容易,但计算值呢?例如,你可能有一个依赖于多个输入的输出属性。让我们假设我们需要跟踪名和姓,这样我们就可以暴露一个全名的属性。那是如何工作的呢?

    计算值("可观察链")

    事实证明,利用JavaScript对继承的支持,我们可以扩展Observable类来处理计算值。这个类需要做一些额外的工作。

    1. 跟踪计算新属性的函数。
    2. 理解依赖性,即观察到的属性,计算出的属性所依赖的。
    3. 订阅依赖关系的变化,以便对计算的属性进行重新评估。

    这个类实现起来比较容易。

    class Computed extends Observable {
      constructor(value, deps) {
        super(value());
        const listener = () => {
          this._value = value();
          this.notify();
        }
        deps.forEach(dep => dep.subscribe(listener));
      }
    
      get value() {
        return this._value;
      }
    
      set value(_) {
        throw "Cannot set computed property";
      }
    } 

    它接收函数和依赖关系,并将初始值作为种子。它监听依赖关系的变化并重新评估计算值。最后,它覆盖了setter,抛出一个异常,因为它是只读的(计算的)。这里是它的使用情况。

    const first = new Observable("Jeremy");
    const last = new Observable("Likness");
    const full = new Computed(() => `${first.value} ${last.value}`.trim(), [first, last]);
    first.value = "Doreen";
    console.log(full.value);
    // logs "Doreen Likness" to the console 

    现在我们可以跟踪我们的数据,但是HTML DOM呢?

    双向数据绑定

    对于双向数据绑定,我们需要用观察到的值来初始化一个DOM属性,并在该值变化时更新它。我们还需要检测DOM更新的时间,以便将新的值传递给数据。使用内置的DOM事件,这就是设置输入元素的双向数据绑定的代码样子。

    const bindValue = (input, observable) => {
        input.value = observable.value;
        observable.subscribe(() => input.value = observable.value);
        input.onkeyup = () => observable.value = input.value;
    } 

    看起来并不难,是吗?假设我有一个输入元素,其id属性设置为first,我可以这样接线。

    const first = new Observable("Jeremy");
    const firstInp = document.getElementById("first");
    bindValue(firstInp, first); 

    其他值也可以重复这样做。

    提示: 当然,这只是一个简单的例子。如果你希望使用数字输入,你可能需要转换数值,并为单选列表等元素编写不同的处理程序,但一般的概念是一样的。

    如果我们能尽量减少代码绑定和声明式数据绑定就更好了。让我们来探讨一下这个问题。

    声明式数据绑定

    我们的目标是避免通过元素的id来加载元素,而是简单地将它们直接绑定到观测值上。我为这个任务选择了一个描述性的属性,并将其称为data-bind。我用一个指向某个上下文上的属性的值来声明这个属性,所以它看起来是这样的。

    <label for="firstName">
      <div>First Name:</div><input type="text" data-bind="first" id="firstName" />
    </label>

    为了把事情连接起来,我可以重用现有的dataBind实现。首先,我设置了一个要绑定的上下文。然后,我配置上下文并应用绑定。

    const bindings = {};
    
    const app = () => {
      bindings.first = new Observable("Jeremy");
      bindings.last = new Observable("");
      bindings.full = new Computed(() => 
          `${bindings.first.value} ${bindings.last.value}`.trim(), 
          [bindings.first, bindings.last]);
      applyBindings();
    };
    
    setTimeout(app, 0); 

    setTimeout给出了初始渲染周期的完成时间。现在我实现了解析声明和绑定声明的代码。

    const applyBindings = () => {
        document.querySelectorAll("[data-bind]").forEach(elem => {
          const obs = bindings[elem.getAttribute("data-bind")];
        bindValue(elem, obs);
      });
    } 

    这段代码抓取每个带有data-bind属性的标签,将其作为索引来引用上下文中的可观察项,然后调用dataBind操作。

    就是这样,我们完成了。我们完成了。下面是完整的实现。

    点击这里 打开完整的代码示例。

    附注:Eval 上下文

    数据绑定并不总是像指向一个观测值的名称那么简单。在许多情况下,你可能想要Eval一个表达式。如果你能约束上下文,使表达式不影响其他表达式或执行不安全的操作,那就更好了。这也是可能的。考虑一下表达式a+b。有几种方法可以 "在上下文中 "约束它。第一种,也是最不安全的,就是在特定上下文中使用eval。下面是示例代码。

    const strToEval = "this.x = this.a + this.b";
    const context1 = { a: 1, b: 2 };
    const context2 = { a: 3, b: 5 };
    const showContext = ctx => console.log(`x=${ctx.x}, a=${ctx.a}, b=${ctx.b}`);
    const evalInContext = (str, ctx) => (function (js) { return eval(js); }).call(ctx, str);
    showContext(context1);
    // x=undefined, a=1, b=2 showContext(context2);
    // x=undefined, a=3, b=5 evalInContext(strToEval, context1);
    evalInContext(strToEval, context2);
    showContext(context1);
    // x=3, a=1, b=2 showContext(context2);
    // x=8, a=3, b=5 

    这使得上下文可以改变,但有几个缺陷。使用 "this "的惯例很笨拙,而且有很多潜在的安全漏洞。只要添加一个window.location.hrefault语句,你就明白了。一个更安全的方法是只允许返回值的Eval,然后把它们包在一个动态函数中。下面的代码就可以做到这一点,而且没有导航的副作用。

    const strToEval = "a + b; window.location.;
    const context1 = { a: 1, b: 2 };
    const context2 = { a: 3, b: 5 };
    const evalInContext = (str, ctx) => (new Function(`with(this) { return ${str} }`)).call(ctx);
    console.log(evalInContext(strToEval, context1));
    // 3 console.log(evalInContext(strToEval, context2));
    // 8 

    通过这个小技巧,你可以在特定的上下文中安全地评估表达式。

    结束语

    我并不反对框架。我已经构建了一些令人难以置信的大型企业Web应用,这些应用的成功很大程度上得益于我们从使用Angular等框架中获得的好处。然而,重要的是要跟上最新的原生进展,不要把框架看成可以解决所有问题的 "黄金工具"。依靠框架意味着通过设置、配置和维护来暴露自己的开销,冒着安全漏洞的风险,而且在很多情况下,还要部署大型的有效载荷。你必须雇佣熟悉该框架细微差别的人才,或者对他们进行培训,并跟上更新的步伐。了解原生代码可能只是为你节省了一个构建过程,并实现了在现代浏览器中 "只是工作 "的场景,而不需要大量的代码。

    一如既往,我欢迎你的反馈、想法、评论和问题。

    致敬。

    系列的一部分。Vanilla.js
    过去我曾写过关于现代Web开发的 "三个D "的文章。

    现代网络开发的三个D

    现代网络开发的三个D

    通过学习依赖注入、声明式语法和数据绑定,了解现代JavaScript框架(如Angular、React和Vue)的历史和分解。

    查看原文

    赞 1 收藏 1 评论 0

    Reco 赞了文章 · 10月26日

    浅谈怎样系统的准备前端面试

    image.png

    前言

    创业梦碎,回归现实,7 月底毅然裸辞,苦战两个月,拿到了美团和字节跳动的 offer,这算是从业以来第一次真正意义的面试,遇到蛮多问题,比如一开始具体的面试过程我都不懂,基本一直是摸着石头过河,所以结合我的经历和前人经验,总结一下我认为还比较系统科学的面试大纲分享给大家,希望大家在系统的准备之后,都能找到自己满意的工作。

    一、知识准备

    1. 知识体系

    知识体系是重中之重,优秀的开发者很多都有维护自身知识体系的习惯,建立知识体系能帮助认知知识全貌及迅速找到知识的关联,就像对碎片化的知识做了索引,无论工作还是面试中碰到的问题,能很快对号入座,举一反三,哪怕是不熟悉的知识点,也可以使用其他同体系的知识进行关联解释,知识体系可以帮助你尽快了解自己,帮助你查漏补缺,让你能够把宝贵的时间聚焦于自己的薄弱项。

    如果还没有自己的知识体系,那就赶快行动起来吧,这里列出一些参考资料帮助你快速行动:

    2. 时间分配

    梳理好知识体系之后,接下来就是制定一个合理的学习计划了,这一步需要你根据自己的个人时间进行安排(我就是时间安排不了一狠心就裸辞了,后面压力巨大),按照知识体系中标记的优先级进行系统的学习,总的时间根据自身情况,建议 1 ~ 3 个月即可,太长时间可能容易遗忘前面学习的知识。

    针对不同模块,时间安排也有所不同,我列举一下我自己的安排以供参考:

    • 基础知识 40%
    • 项目与业务 20%
    • 算法与数据结构 20%
    • 设计思想与工程化 10%
    • 框架与原理 10%

    3. 整理算法

    这里把算法单独拿出来,是因为近年来在大厂的面试中对数据结构和算法的考察越来越重视,不管是前端还是后端,首先我们是工程师,我们日常工作就是写程序的,程序 = 数据结构 + 算法,所以算法和数据结构的学习是很有必要的,虽然对于前端岗位的算法要求可能不会那么高,但是基本的递归、遍历、链表的操作、栈与队列的常见算法还是要会的。每天学习两三题,两个月后,你不会后悔的。

    推荐一些社区内很不错的算法学习资料和经验:

    喜欢付费课程的话,比较不错的有:

    • 慕课网 bobo 老师的《算法与数据结构体系课》
    • 极客时间 覃超 老师的《算法面试通关40讲》

    4. 整理面试题

    这一步不是让大家去只刷面试题,而是熟悉目前实际面试中常见的考察方式和知识点,做到心中有数,也可以用来自查及完善知识体系。可以搜集整理近两年来一线公司的面试题,做成笔记,你会发现面试题实际问的大同小异,只是考察的内容和形式有不同的目的性。提前熟悉,上场的时候才不会慌张。

    推荐资料:

    5. 常见功能的手写实现

    这块几乎是必考的,比如:深拷贝、事件总线、es5 继承,以及最近很火的手写 Promise 实现,这些手写功能不仅考察了面试者的编码能力也考察了对原理和规范的掌握程度。

    虽然实际面试过程,面试官可能不会问的特别细节,比如让你实现一个完整的 Promise,但是我们自己学习这些手写功能的时候,不能浅尝辄止,需要考虑使用场景、错误处理、规范等细节的问题,千万不要背代码,不然手写代码一时爽,深挖细节火葬场。

    笔者自己粗略的总结了一些前端面试常见的手写功能,供大家参考;

    6. 项目实战

    这部分是社招必考点,对自己负责或参与的项目,一定要深挖,要提炼出「难点」「痛点」「亮点」以及「解决方案」,更要体现出自己的「思考」和做出的「努力」,对应于 「问题 - 思考 - 解决 - 成果」这样的一个过程,是大厂很看重的能力,希望大家对于自己的参与的项目都能参考这个流程进行思考总结。

    如果没有很丰富的项目经验,也可以多研究社区内技术大佬们的「项目经验」来获得,但一定要研究透彻,看过不等于会,不然面试问到只会坑了自己。

    7. 执行学习计划

    按照梳理的「知识体系」、「整理算法」、以及「整理面试题」,结合「时间分配」、给自己制定一个合适的学习计划,然后坚定认真的去执行它。

    二、简历准备

    1. 参考目录

    • 【基本信息】不写无用的个人信息,比如:照片、籍贯、性别、地址、身高等;
    • 【技术技能】对「了解、熟练掌握、精通」这类词有概念,不要随便用精通;
    • 【项目经历】不写对求职无用项目经历、做的最好的项目 2 ~ 3 个即可,有数据支撑;
    • 【工作经历】简略概述名称、在职时间、职位以及主要负责业务,也可以和项目经历结合起来写;
    • 【教育背景】名称、时间等简要信息,普通院校建议放在底部即可,名校可以放在顶部;

      参考简历模板:链接: https://pan.baidu.com/s/1I-9U... 提取码: gkxw

    2. 项目经历

    简历中最难写的应该就是「项目经历」了,这块也是最重要的,是面试官考察你的依据,也是你用来引导面试官提问的工具,项目经历的总结,要有数据思维,不能泛泛而谈,一般按照 STAR 法则进行描述,按照:情境(situation)、目标(target)、行动(action)、结果(result)四项对工作做一个精简描述,例如:

    • 项目简介以及在 xx 项目中担任前端负责人
    • 负责了 xxx 工作,实现什么目标
    • 通过 xxx 方案解决了 xxx 问题;使 xxx 提升了 50%
    • 总结了 xxx 解决方案

    3. 注意事项

    • 【突出亮点】如开源项目、大厂背景、社区影响力、知名项目、个人博客、技术亮点等;
    • 【对症下药】针对不同公司职位,可以针对性的调整简历内容,准备多份简历;
    • 【格式排版】PDF 格式,最好一页,最多不超过两页,像对待毕业论文一样去检查排版、错别字、标点符号、措辞;
    • 【文件命名】姓名_职位_手机号.pdf(学历有优势的可以加上最高学历院校);

    4. 投递简历

    • 【筛选公司】相关因素:平台大小、发展前景、公司距离、个人喜好等;
    • 【了解公司】通过网络、社区、认识的内部员工去了解面试的具体流程、周期、注意事项等;
    • 【投递顺序】面试周期短的可以推迟,面试周期长的可以先面试,保证 offer 发放之间的时间跨度不会太长,便于集中对比选择;最想去的公司可以最后面试,这时已充分热身,甚至拿了 offer,心态方面也会更加从容;
    • 【内推优先】优先找人内推,社区内有很多小伙伴愿意帮忙的;

    三、面试准备

    1. 自我介绍

    面试官对你的基本信息都已知晓,所以这一步最好结合应聘职位直奔亮点进行简要概述,做了哪些亮点项目遇到了什么难点如何解决的项目有什么收获给团队或公司带来了哪些成果,按照这个方式去吸引面试官,同时这也是我们掌握主动权的方法,面试官喜欢根据我们表述的内容进行展开,这样由一段精心设计的自我介绍开始,进而引导面试官和你交流,这会让面试官的工作开展的很舒服。

    注意自我介绍不必涉及过多的技术细节阐述,一是这些技术细节可能面试官不一定涉猎,导致面试官只能从其他方面寻找切入点让你陷入被动,二是占用过多时间,所以简要概述要点即可,随后面试官会根据这些点和你展开沟通的,这时再详细阐述不迟。

    2. 面试过程

    大厂的面试多为四轮,整个过程因人因公司而异,下面介绍一些常见的面试过程与注意事项:

    一面

    一般是你应聘职位的平级的骨干同事,是入职后和你一起并肩作战的伙伴,这一面一般也是最难的,会从多个方面考察你能不能胜任这份工作,侧重于学习能力、沟通能力、基础知识掌握程度、总结与思考、编码能力等;

    这一面要特别注意编程题,如果遇到原题,不要太激动,面试官会从其他方面再进一步考察你,所以多思考一点,这也是上文说的,一定不要背题,不然一问就露馅;

    遇到不会的知识,也不要太紧张,先尝试暴力解,然后逐步优化,也可以请面试官给予提示,如果能在面试中解决一个不会的问题,那一定会让面试官给你加分的;

    二面

    一般是团队骨干或直属 leader,这一面是对一面的延伸,除了基础知识之外,面试官还会从技术选型、架构、解决方案等方面提问,考察你对技术细节、项目优化、整体方案等方面的思考;

    三面

    三面一般是所属团队的 leader,这一面的技术细节考察你的不会太多,更多的是你对工作中涉及到的业务、产品、技术的思考,职业的规划与个人发展,以及一些职场软技能,常见问题举例:

    • 项目中的角色、承担了哪些任务、遇到了哪些难点?怎么克服的?
    • 和其他技术选型或者产品项目的对比有什么优劣?
    • 团队怎么协作与分工的?
    • 给自己的技术能力做一个评价
    • 做了哪些提升团队的工作?
    • 公司产品这样的?是否有竞争力?怎么盈利的?
    • 你的个人职业规划?

    四面

    四面一般是 HR 面,这一面,尽力别说太多题外话,因为言多必失,保持积极乐观、礼貌友好的态度,当面试官问你为什么离职时,哪怕你上家公司老板和你打过架,也不要抱怨说出来。常见问题举例:

    • 为什么从上家公司离职?(注意积极向上)
    • 希望找一个怎样的工作,职业规划呢?(重发展,少谈钱)
    • 谈谈自己最大的优点?(不要编,结合实际说就好,大家都有的,比如:专注、团队精神、技术热情与钻研精神、沟通能力、深度思考等)
    • 谈谈自己最大的缺点?(和工作相关,又可以通过努力改变的点,比如:过于局限技术细节而忽视产品业务的重要性和理解,导致开发过程受阻,现在会积极参与产品业务的早起阶段,加强对业务的理解)
    • 方便透露手上都有哪些 offer 了吗?(按实际情况说即可,好的 offer 可以突出一下,不好的,可以不说)
    • 在 B 公司和我们之间,你怎么考虑的呢?(肯定选你啦)

    3. 面试官:“你有什么想问我的吗?”

    这个问题一般每一面都会遇到的,提问是面试中我们能够主动“索取”的环节,所以一定不要浪费这个机会,一些 leader 的回答还会带给你很多技术之外的思考与经验,让你受益良多,一定注意,不要问和待遇相关的问题,最后谈 offer 的时候再去问。

    下面给出一些提问示例供参考:

    • 一面:面试官一般是你的平级同事,可以多去了解实际的工作内容,便于后续对比 offer,例如:团队业务、日常工作、技术栈、协作、技术分析等
    • 二面:面试官一般是团队骨干或直属 leader,可以多去了解业务和产品的规划、技术建设、对应聘职位的定位与期待等;
    • 三面:面试官一般是部门 leader,这一步可以多了解技术之外的知识,比如面试官自己的成长经验、技术之外的能力、职位发展路线等;
    • Hr 面:这一步可以多去了解公司本身相关的事,比如:你在公司工作的最大的感受是什么?晋升机制是怎样的?等等

    4. 面试复盘

    面试也是一个特别好的学习过程,能利用这个机会和其他团队的优秀的人沟通技术、交流心得、检验能力、了解优秀团队业务和产品,无论最后结果怎样,都值得好好总结下来。

    • 【记录】每轮面试结束后,尽量详细记录整个过程,最好录音,方便分析自己的表现
    • 【分析】按照自己的掌握程度对面试问题进行分类统计,分析沟通过程以及自己的表现
    • 【补强】一知半解的问题优先复习掌握,不会的问题要去大致了解一下,如果没有时间掌握,可以暂时忽略
    • 【总结】分析补强之后,可以总结成文,也可以分享给社区的小伙伴

    五、Offer

    当面试通过以后,你就要着手开始准备最后的 offer 沟通了,这一步,你要结合新公司的薪资构成,职位的薪资范围,自己估算涨幅后的年薪总包、社区了解的信息、公司发展前景、个人心里预期等去设定一个自己的薪资底线。

    1. 年薪总包,是你在上家公司的税前年度总收入:『月薪 * 12 + 奖金 + 其他』,会要求银行流水进行证明,新公司会参考进行定薪,特殊情况可以主动说明,上一家公司的薪资知识参考,但也不是决定因素的。

    2. 薪资谈判,这一步可以说是最考验沟通能力的环节了,这里提供一些信息:

    个人实力 / 公司水平优秀公司普通公司
    个人实力优秀保持底线、冲击高薪没有底线、必须高薪
    个人实力普通降低底线、学习为重保持底线、冲击高薪
    • 薪资一般会在之前总包的基础上提高 30% ~ 50%;
    • 实力优秀且入职优秀公司,翻倍不是不可能;
    • 有些公司超过 50% 涨幅需要走特批,要求你承诺入职才会给你申请,这个说明一是公司对你认可,二是公司希望你尽快入职,所以如果公司不错,可以好好考虑一下;
    • HR 询问薪资预期时,可以基于心里底线和职位薪资范围向上多要一些,大大方方的沟通即可,没有知乎上说的那么多戏,与其说 HR 压价,倒不如说 HR 是防止候选人狮子大开口,只要薪资的提升在一个合理的范围,谈薪还是比较简单的。
    • HR 询问薪资时,一般还会问你都拿到了哪些 offer 了,如果你手上有比较不错的 offer 可以说一下,可以帮助 HR 更加确定你是一个优秀的候选人,薪资说不定还可以获得一个提升,但是一定不要为了提价胡编乱造 offer,诚信是本;

    3. 何时入职,这一步,HR 都会问你何时能入职,这个结合你的当前工作和后续的面试计划,可以推迟入职日期,但是不宜太久,后续如果不能入职,一定要尽早通知 HR,要尊重别人的工作与付出,礼貌说明原因即可,别让人家等太久;

    4. offer 对比,简单来说:有目标向前看,没目标向钱看,薪资很重要,但是技术人的职业发展更重要,而且大的平台在薪资上也不会让你吃亏;

    六、注意事项

    • 不要裸辞,不要裸辞,不要裸辞;
    • 尽量 15 号之后办理离职,下个月的 15 号之前入职,这样能保证你的五险一金不断缴,平滑过度
    • 上家公司签的离职证明日期,不要和新公司的入职日期有重叠,所以拿到 offer 后,要留出时间先把当前工作的离职手续办完;
    • 注意社交礼仪,IT 行业虽然没那么多繁文缛节,但是基本的礼节不能丢,着装得体整洁、不要迟到、进门敲门、出门关门、等;
    • 有 offer,心不慌,可以先把还不错的 offer 留着,再去冲击大厂;
    • 面试是七分实力三分运气,不同的面试侧重点也会不同,所以不要因为某一两次面试受挫就丢失信心,及时总结;
    • 乐观积极、保持诚信、杜绝欺骗、避免负面情绪;
    • 不抱怨同事、不抱怨上家公司;

    七、扯点别的

    感谢一下 @ssh_晨曦时梦见兮 给我内推,给我看简历,给我建议,一句:“我觉得你的简历还不错”,给了我很大信心,和晨曦开玩笑说:“自从遇到你好像所有的面试都顺利多了,争取做你同事”,最后虽然不在一个部门,也算是得偿所愿成了同事,就等面基吃饭啦。

    感谢一下 @狼叔 * 阿里巴巴,和狼叔都有着一段困难的创业公司经历,在我辞职之后一直走不出内心对未来迷茫以及对过去痛心的情况下,我主动联系了狼叔,对于我的现状和规划,狼叔谈了自己的看法,给了建议,鼓励我:“有目标向前看,没目标向钱看,现在都还不晚,加油吧”。

    最后手握 offer 后,甚至有点做梦的感觉,一路走来,一直感觉迷茫与无助,直到最后严重怀疑自己,但离职的这段时间,有不少朋友经常鼓励我:“自信点,你还不错,加油!”,也一直帮我找内推,我觉得没有这些朋友,我心态可能没那么快恢复过来,真的非常感谢他们。

    稳定之后,我现在除了做好工作以外,还想做的一件事就是也试着去帮助一些我能帮助的人,也希望有机会鼓励他们:“自信点,你很棒,加油!”

    八、说在最后

    文章主要对面试的核心流程与准备工作做了一个大纲性的概述,重点在于对面试的一个整体的审视以及各个环节的重点,所以肯定有很多细节没有顾及到,如有疑问或者建议也欢迎留言一起交流讨论,也欢迎联系我,找内推、聊简历、聊技术、侃大山。

    邮箱:weboying@gmail.com
    公众号:iboying

    image.png

    查看原文

    赞 49 收藏 35 评论 4

    Reco 发布了文章 · 10月13日

    node-net-snmp模块手册

    net-snmp

    本模块实现了简单网络管理协议(SNMP)的1、2c和3版本。

    该模块使用node package manager (npm)安装。

    npm install net-snmp 

    使用require()函数加载。

    var snmp = require ("net-snmp"); 

    然后可以创建到远程主机的会话,并用于执行SNMP请求和发送SNMP陷阱或通知。

    var session = snmp.createSession ("127.0.0.1", "public");
    
    var oids = ["1.3.6.1.2.1.1.5.0", "1.3.6.1.2.1.1.6.0"];
    
    session.get (oids, function (error, varbinds) {
        if (error) {
            console.error (error);
        } else {
            for (var i = 0; i < varbinds.length; i++)
                if (snmp.isVarbindError (varbinds[i]))
                    console.error (snmp.varbindError (varbinds[i]))
                else
                    console.log (varbinds[i].oid + " = " + varbinds[i].value);
        }
        session.close ();
    });
    
    session.trap (snmp.TrapType.LinkDown, function (error) {
        if (error)
            console.error (error);
    }); 

    应用

    RFC 3413描述了五种类型的SNMP应用。

    1. 命令生成器应用程序--发起读写请求
    2. 命令应答器应用程序--对收到的读或写请求作出反应。
    3. 通知发起者应用程序 -- -- 产生通知(陷阱或通知)。
    4. 接收通知的应用程序 -- -- 接收通知(陷阱或通知)。
    5. 代理转发应用--转发SNMP信息。

    使用该模块 - 命令和通知生成器

    该库提供了一个Session类,为建立 "命令生成器 "和 "通知发起者 "SNMP应用程序提供支持。

    所有的SNMP请求都是使用Session类的实例进行的。本模块输出两个函数,用于创建Session类的实例。

    • createSession() - for v1 and v2c sessions
    • createV3Session() - for v3 sessions

    snmp.createSession ([target], [community], [options])

    createSession()函数实例化并返回一个SNMPv1或SNMPv2c的Session类实例。

    // Default options
    var options = {
        port: 161,
        retries: 1,
        timeout: 5000,
        backoff: 1.0,
        transport: "udp4",
        trapPort: 162,
        version: snmp.Version1,
        backwardsGetNexts: true,
        idBitsSize: 32
    };
    
    var session = snmp.createSession ("127.0.0.1", "public", options); 

    可选的target参数默认为127.0.0.1。可选的 "community "参数默认为 "public"。可选的options参数是一个对象,可以包含以下项目。

    • port - 发送请求的UDP端口,默认为161
    • "reties" -- -- 重新发送请求的次数,默认为 "1"。
    • sourceAddress----SNMP请求应来自的IP地址,该选项没有默认值,操作系统将在发送SNMP请求时选择一个适当的源地址。
    • sourcePort - UDP端口,SNMP请求应从该端口发出,默认为操作系统选择的短暂端口。
    • timeout -- -- 在重新尝试或失败之前等待响应的毫秒数,默认值为5000
    • "backoff" -- -- 每次重试时增加 "超时 "的系数,不增加时默认为 "1"。
    • transport -- -- 指定要使用的传输,可以是udp4udp6,默认为udp4
    • trapPort -- -- 发送陷阱和通知的UDP端口,默认值为162
    • version - snmp.Version1snmp.Version2c,默认为snmp.Version1
    • "backwardsGetNexts"----允许进行GetNext操作的布尔值,以检索词法上在前的OIDs。
    • idBitsSize - 1632,默认为32。用来减少生成的id的大小,以便与一些旧设备兼容。

    当一个会话结束后,应该关闭它。

    session.close (); 

    snmp.createV3Session (target, user, [options])

    createV3Session()函数实例化并返回一个与createSession()相同的Session类实例,只是为SNMPv3进行了初始化。

    // Default options for v3
    var options = {
        port: 161,
        retries: 1,
        timeout: 5000,
        transport: "udp4",
        trapPort: 162,
        version: snmp.Version3,
        idBitsSize: 32,
        context: ""
    };
    
    // Example user
    var user = {
        name: "blinkybill",
        level: snmp.SecurityLevel.authPriv,
        authProtocol: snmp.AuthProtocols.sha,
        authKey: "madeahash",
        privProtocol: snmp.PrivProtocols.des,
        privKey: "privycouncil"
    };
    
    var session = snmp.createV3Session ("127.0.0.1", user, options); 

    targetuser参数是强制性的。可选的 "options "参数与 "createSession() "调用的含义相同。选项参数中一个额外的字段是context字段,它为会话添加一个SNMPv3上下文。

    user对象必须包含namelevel字段。level字段可以从snmp.SecurityLevel对象中取值。

    • snmp.SecurityLevel.noAuthNoPriv - 不进行消息认证或加密。
    • snmp.SecurityLevel.authNoPriv -- -- 用于信息认证,不进行加密。
    • snmp.SecurityLevel.authPriv -- -- 用于信息认证和加密。

    这些字段的含义符合RFC3414的规定。如果提供的 "level "是 "authNoPriv "或 "authPriv",那么 "authProtocol "和 "authKey "字段也必须存在。authProtocol字段可以从snmp.AuthProtocols对象中取值。

    • snmp.AuthProtocols.md5 - 用于MD5消息认证。
    • snmp.AuthProtocols.sha -- -- 用于SHA信息认证。

    如果提供的 "level "是 "authPriv",那么 "privProtocol "和 "privKey "字段也必须存在。privProtocol字段可以从snmp.PrivProtocols对象中取值。

    • snmp.PrivProtocols.des - 用于DES加密。
    • snmp.PrivProtocols.aes -- -- 用于AES加密。

    一旦创建了v3会话,就可以使用与v1和v2c相同的一组 "会话 "方法。

    session.on ("close", callback)

    当会话的底层UDP套接字被关闭时,会话会发出 "close "事件。

    没有参数传递给回调。

    在该事件发出之前,所有未完成的请求都会被取消,导致每个未完成的请求失败。传回给每个请求的错误将是一个Error类的实例,其错误message属性设置为Socket forcibly closed

    下面的例子是当一个会话的底层UDP套接字被关闭时,打印一条消息到控制台。

    session.on ("close", function () {
        console.log ("socket closed");
    }); 

    session.on ("error", callback)

    当会话的底层UDP套接字发出错误时,会话会发出 "error "事件。

    以下参数将被传递给 "回调 "函数。

    • error - Error类的一个实例,暴露的message属性将包含一个详细的错误信息。

    下面的例子是当一个会话的底层UDP套接字发生错误时,打印一条消息到控制台,然后关闭该会话。

    session.on ("error", function (error) {
        console.log (error.toString ());
        session.close ();
    }); 

    session.close ()

    close()方法关闭UDP套接字的底层会话。这将导致会话底层UDP套接字发出 "close "事件,并传递给会话,导致会话也发出 "close "事件。

    下面的例子关闭了一个UDP套接字底层会话。

    session.close (); 

    session.get (oids, callback)

    get()方法获取一个或多个OID的值。

    oids参数是一个OID字符串数组。一旦请求完成,callback函数就会被调用。以下参数将被传递给callback函数。

    • error - Error类或子类的实例,如果没有发生错误,则为null
    • varbinds - varbinds数组,如果发生错误将不提供。

    varbinds数组中位置N的varbind将对应于请求中oids数组中位置N的OID。

    当使用SNMP版本2c时,必须使用snmp.isVarbindError()函数检查每个varbind是否存在错误条件。

    以下示例获取sysName(1.3.6.1.2.1.1.5.0)和sysLocation(1.3.6.1.2.1.1.6.0)OIDs的值。

    var oids = ["1.3.6.1.2.1.1.5.0", "1.3.6.1.2.1.1.6.0"];
    
    session.get (oids, function (error, varbinds) {
        if (error) {
            console.error (error.toString ());
        } else {
            for (var i = 0; i < varbinds.length; i++) {
                // for version 1 we can assume all OIDs were successful
                console.log (varbinds[i].oid + "|" + varbinds[i].value);
            
                // for version 2c we must check each OID for an error condition
                if (snmp.isVarbindError (varbinds[i]))
                    console.error (snmp.varbindError (varbinds[i]));
                else
                    console.log (varbinds[i].oid + "|" + varbinds[i].value);
            }
        }
    }); 

    session.getBulk (oids, [nonRepeaters], [maxRepetitions], callback)

    getBulk()方法获取MIB树中一个或多个OID后按词法排列的OID的值。

    oids参数是一个OID字符串的数组。可选的nonRepeaters参数指定oids参数中只应返回1个varbind的OID的数量,默认为0。对于oids参数中剩余的每一个OID,可选的maxRepetitions参数指定了一个OID后面的词法OID的数量,对于这些OID,varbind应该被获取,默认值为20

    一旦请求完成,callback函数就会被调用。以下参数将被传递给callback函数。

    • error - Error类或子类的实例,如果没有发生错误,则为null
    • varbinds - varbinds数组,如果发生错误将不提供。

    varbinds数组中N位置的varbind将与请求中oids数组中N位置的OID相对应。

    对于varbinds中的第一个nonRepeaters项目,每个项目将是一个单一的varbind。对于varbinds中所有剩余的项目,每个项目将是一个varbinds数组--这使得将响应的varbinds与请求的OID绑定起来很容易,因为响应的varbinds被分组并放在varbinds中的相同位置。

    当使用SNMP版本2c时,必须使用snmp.isVarbindError()函数检查每个varbind是否存在错误条件。

    下面的例子获取sysContact(1.3.6.1.2.1.4.0)和sysName(1.3.6.1.2.1.5. 0)OID,以及ifTable(1.3.6.1.2.1.2.1.2)表中ifDescr(1.3.6.1.2.1.2.1.2)和ifType(1.3.6.1.2.1.2.1.3)列中最多前20个OID。

    var oids = [
        "1.3.6.1.2.1.1.4.0",
        "1.3.6.1.2.1.1.5.0",
        "1.3.6.1.2.1.2.2.1.2",
        "1.3.6.1.2.1.2.2.1.3"
    ];
     
    var nonRepeaters = 2;
     
    session.getBulk (oids, nonRepeaters, function (error, varbinds) {
        if (error) {
            console.error (error.toString ());
        } else {
            // step through the non-repeaters which are single varbinds
            for (var i = 0; i < nonRepeaters; i++) {
                if (i >= varbinds.length)
                    break;
     
                if (snmp.isVarbindError (varbinds[i]))
                    console.error (snmp.varbindError (varbinds[i]));
                else
                    console.log (varbinds[i].oid + "|" + varbinds[i].value);
            }
     
            // then step through the repeaters which are varbind arrays
            for (var i = nonRepeaters; i < varbinds.length; i++) {
                for (var j = 0; j < varbinds[i].length; j++) {
                    if (snmp.isVarbindError (varbinds[i][j]))
                        console.error (snmp.varbindError (varbinds[i][j]));
                    else
                        console.log (varbinds[i][j].oid + "|"
                                + varbinds[i][j].value);
                }
            }
        }
    });

    session.getNext (oids, callback)

    getNext()方法获取MIB树中一个或多个OID后按词法排列的OID的值。

    oids参数是一个OID字符串的数组。一旦请求完成,就会调用callback函数。以下参数将被传递给callback函数。

    • error - Error类或子类的实例,如果没有发生错误,则为null
    • varbinds - varbinds数组,如果发生错误将不提供。

    varbinds数组中位置N的varbind将对应于请求中oids数组中位置N的OID。

    当使用SNMP版本2c时,必须使用snmp.isVarbindError()函数检查每个varbind是否存在错误条件。

    下面的例子获取sysObjectID(1.3.6.1.1.2.1.1.0)和sysName(1.3.6.1.1.2.1.4.0)OID之后的下一个OID的值。

    var oids = [
        "1.3.6.1.2.1.1.1.0",
        "1.3.6.1.2.1.1.4.0"
    ];
    
    session.getNext (oids, function (error, varbinds) {
        if (error) {
            console.error (error.toString ());
        } else {
            for (var i = 0; i < varbinds.length; i++) {
                // for version 1 we can assume all OIDs were successful
                console.log (varbinds[i].oid + "|" + varbinds[i].value);
            
                // for version 2c we must check each OID for an error condition
                if (snmp.isVarbindError (varbinds[i]))
                    console.error (snmp.varbindError (varbinds[i]));
                else
                    console.log (varbinds[i].oid + "|" + varbinds[i].value);
            }
        }
    }); 

    session.inform (typeOrOid, [varbinds], [options], callback)

    inform()方法发送一个SNMP信息。

    typeOrOid参数可以是两种类型之一:snmp.TrapType对象中定义的常量之一(不包括snmp.TrapType.EnterpriseSpecific常量),或者是一个OID字符串。

    在请求消息中放置的第一个varbind将是sysUptime.0 OID(1.3.6.1.2.1.1.3.0)。这个varbind的值将是process.uptime()函数返回的值乘以100(这可以通过在可选的options参数中提供upTime来覆盖,如下文所述)。

    这之后会有第二个varbind的snmpTrapOID.0 OID (1.3.6.1.6.3.1.1.4.1.0)。这个值取决于typeOrOid参数。如果指定了一个常量,那么常量的陷阱OID将被用来作为varbinds的值,否则指定的OID字符串将被用来作为varbind的值。

    可选的 "varbinds "参数是要包含在信息请求中的varbinds数组,默认为空数组[]

    可选的options参数是一个对象,可以包含以下项目。

    • upTime - inform中sysUptime.0 OID(1.3.6.1.2.1.1.3.0)的值,默认为process.uptime()函数返回的值乘以100。

    一旦收到对信息请求的答复或发生错误,就会调用 "回调 "函数。以下参数将被传递给callback函数。

    • error - Error类或子类的实例,如果没有发生错误,则为null
    • varbinds - varbinds数组,如果发生错误将不提供。

    varbinds数组中N位置的varbind将与请求中varbinds数组中N位置的varbind相对应。远程主机应该按照请求中的指定回传varbinds和它们的值,varbinds数组将包含远程主机发回的每个varbind。

    通常没有理由使用varbinds参数的内容,因为varbinds是在请求中发送的。

    下面的例子发送了一个通用的冷启动信息给远程主机,它不包含任何varbinds。

    session.inform (snmp.TrapType.ColdStart, function (error) {
        if (error)
            console.error (error);
    }); 

    下面的例子是向远程主机发送一个企业特定的信息,并包含两个企业特定的varbinds。

    var informOid = "1.3.6.1.4.1.2000.1";
    
    var varbinds = [
        {
            oid: "1.3.6.1.4.1.2000.2",
            type: snmp.ObjectType.OctetString,
            value: "Periodic hardware self-check"
        },
        {
            oid: "1.3.6.1.4.1.2000.3",
            type: snmp.ObjectType.OctetString,
            value: "hardware-ok"
        }
    ];
    
    // Override sysUpTime, specfiying it as 10 seconds...
    var options = {upTime: 1000};
    session.inform (informOid, varbinds, options, function (error) {
        if (error)
            console.error (error);
    }); 

    session.set (varbinds, callback)

    set()方法设置一个或多个OID的值。

    varbinds参数是一个varbind对象的数组。一旦请求完成,就会调用callback函数。以下参数将被传递给callback函数。

    • error - Error类或子类的实例,如果没有发生错误,则为null
    • varbinds - varbinds数组,如果发生错误将不提供。

    varbinds数组中N位置的varbind将与请求中varbinds数组中N位置的varbind相对应。除非发生错误,否则远程主机应该按照请求中指定的varbinds和它们的值回传。varbinds数组将包含远程主机发回的每个varbind。

    当使用SNMP版本2c时,必须使用snmp.isVarbindError()函数检查每个varbind是否存在错误条件。

    下面的例子设置了sysName(1.3.6.1.2.1.1.4.0)和sysLocation(1.3.6.1.2.1.1.6.0)OID的值。

    var varbinds = [
        {
            oid: "1.3.6.1.2.1.1.5.0",
            type: snmp.ObjectType.OctetString,
            value: "host1"
        }, {
            oid: "1.3.6.1.2.1.1.6.0",
            type: snmp.ObjectType.OctetString,
            value: "somewhere"
        }
    ];
    
    session.set (varbinds, function (error, varbinds) {
        if (error) {
            console.error (error.toString ());
        } else {
            for (var i = 0; i < varbinds.length; i++) {
                // for version 1 we can assume all OIDs were successful
                console.log (varbinds[i].oid + "|" + varbinds[i].value);
            
                // for version 2c we must check each OID for an error condition
                if (snmp.isVarbindError (varbinds[i]))
                    console.error (snmp.varbindError (varbinds[i]));
                else
                    console.log (varbinds[i].oid + "|" + varbinds[i].value);
            }
        }
    }); 

    session.subtree (oid, [maxRepetitions], feedCallback, doneCallback)

    subtree()方法获取MIB树中以指定的OID为基础,按词法排列在指定OID之后的所有OID的值。例如,sysName(1.3.6.1.2.1.1.5.0)和sysLocation(1.3.6.1.2.1.1.6.0)这两个OID都有相同的基系统(1.3.6.1.2.1.1)OID。

    对于SNMP版本1,重复调用get(),直到返回的一个OID不使用指定的OID作为其基础。对于SNMP版本2c,重复调用getBulk(),直到返回的OIDs中没有使用指定的OID作为其基础。

    oid参数是一个OID字符串。当使用SNMP版本2c时,可选的maxRepetitions参数被传递给getBulk()请求。

    一旦获取了所有的OID值,这个方法将不会调用一个回调。相反,feedCallback函数将在每次从远程主机收到响应时被调用。以下参数将被传递给feedCallback函数。

    • varbinds - varbinds数组,至少包含一个varbind。

    当使用SNMP版本2c时,必须使用snmp.isVarbindError()函数检查每个varbind是否存在错误条件。

    一旦返回的OID中至少有一个没有使用指定的OID作为其基础,或者发生了错误,doneCallback函数将被调用。以下参数将被传递给doneCallback函数。

    • error - Error类或子类的实例,如果没有发生错误,则为null

    一旦doneCallback函数被调用,请求就完成了,feedCallback函数将不再被调用。

    如果feedCallback函数调用时返回true值,则不再调用get()getBulk()方法,调用doneCallback

    下面的例子是获取系统(1.3.6.1.2.1.1)下的所有OID。

    var oid = "1.3.6.1.2.1.1";
    
    function doneCb (error) {
        if (error)
            console.error (error.toString ());
    }
    
    function feedCb (varbinds) {
        for (var i = 0; i < varbinds.length; i++) {
            if (snmp.isVarbindError (varbinds[i]))
                console.error (snmp.varbindError (varbinds[i]));
            else
                console.log (varbinds[i].oid + "|" + varbinds[i].value);
        }
    }
    
    var maxRepetitions = 20;
    
    // The maxRepetitions argument is optional, and will be ignored unless using
    // SNMP verison 2c
    session.subtree (oid, maxRepetitions, feedCb, doneCb); 

    session.table (oid, [maxRepetitions], callback)

    table()方法获取MIB树中以指定的OID为基础的、按词法排列在指定OID之后的所有OID的值,这与subtree()方法很相似。

    这个方法被设计用来获取概念表,例如ifTable(1.3.6.1.2.1.2.2)表。返回的varbinds的值将被结构化为代表概念行的对象。然后将每一行放入一个对象中,行的索引是键,例如:

    var table = {
        // Rows keyed by ifIndex (1 and 2 are shown)
        1: {
            // ifDescr (column 2) and ifType (columnd 3) are shown
            2: "interface-1",
            3: 6,
            ...
        },
        2: {
            2: "interface-2",
            3: 6,
            ...
        },
        ...
    } 

    本方法内部调用subtree()方法来获取指定表的子树。

    oid参数是一个OID字符串。如果传递的OID字符串不代表一个表,那么产生的用于保存表数据的对象将是空的,也就是说,它将不包含索引和行。可选的maxRepetitions参数被传递给subtree()请求。

    一旦整个表被获取,callback函数将被调用。以下参数将被传递给callback函数。

    • error - Error类或子类的实例,如果没有发生错误,则为null
    • table -- -- 包含对象引用,代表按索引键入的概念行(例如,ifTable表的行按ifIndex键入),每个行对象将包含按列号键入的值,如果发生错误将不提供。

    如果subtree()返回的任何varbind发生错误,将不会向callback函数传递任何表。失败的原因和相关的OID字符串(从调用snmp.varbindError()函数返回的),将作为RequestFailedError类的一个实例,在error参数中传递给callback函数。

    下面的例子获取ifTable(1.3.6.1.2.1.2.2)表。

    var oid = "1.3.6.1.2.1.2.2";
    
    function sortInt (a, b) {
        if (a > b)
            return 1;
        else if (b > a)
            return -1;
        else
            return 0;
    }
    
    function responseCb (error, table) {
        if (error) {
            console.error (error.toString ());
        } else {
            // This code is purely used to print rows out in index order,
            // ifIndex's are integers so we'll sort them numerically using
            // the sortInt() function above
            var indexes = [];
            for (index in table)
                indexes.push (parseInt (index));
            indexes.sort (sortInt);
            
            // Use the sorted indexes we've calculated to walk through each
            // row in order
            for (var i = 0; i < indexes.length; i++) {
                // Like indexes we sort by column, so use the same trick here,
                // some rows may not have the same columns as other rows, so
                // we calculate this per row
                var columns = [];
                for (column in table[indexes[i]])
                    columns.push (parseInt (column));
                columns.sort (sortInt);
                
                // Print index, then each column indented under the index
                console.log ("row for index = " + indexes[i]);
                for (var j = 0; j < columns.length; j++) {
                    console.log ("   column " + columns[j] + " = "
                            + table[indexes[i]][columns[j]]);
                }
            }
        }
    }
    
    var maxRepetitions = 20;
    
    // The maxRepetitions argument is optional, and will be ignored unless using
    // SNMP verison 2c
    session.table (oid, maxRepetitions, responseCb); 

    session.tableColumns (oid, columns, [maxRepetitions], callback)

    tableColumns()方法实现了与table()方法相同的接口。但是,只有在columns参数中指定的列才会出现在生成的表中。

    当只需要选定的列时,应该使用这个方法,并且会比table()方法快很多倍,因为会这会减少提取的数据量。

    下面的例子是获取ifTable(1.3.6.1.2.1.2.2)表,并指定只获取ifDescr(1.3.6.1.2.1.2.1.2)和ifPhysAddress(1.3.6.1.2.1.2.1.6)列。

    var oid = "1.3.6.1.2.1.2.2";
    var columns = [2, 6];
    
    function sortInt (a, b) {
        if (a > b)
            return 1;
        else if (b > a)
            return -1;
        else
            return 0;
    }
    
    function responseCb (error, table) {
        if (error) {
            console.error (error.toString ());
        } else {
            // This code is purely used to print rows out in index order,
            // ifIndex's are integers so we'll sort them numerically using
            // the sortInt() function above
            var indexes = [];
            for (index in table)
                indexes.push (parseInt (index));
            indexes.sort (sortInt);
            
            // Use the sorted indexes we've calculated to walk through each
            // row in order
            for (var i = 0; i < indexes.length; i++) {
                // Like indexes we sort by column, so use the same trick here,
                // some rows may not have the same columns as other rows, so
                // we calculate this per row
                var columns = [];
                for (column in table[indexes[i]])
                    columns.push (parseInt (column));
                columns.sort (sortInt);
                
                // Print index, then each column indented under the index
                console.log ("row for index = " + indexes[i]);
                for (var j = 0; j < columns.length; j++) {
                    console.log ("   column " + columns[j] + " = "
                            + table[indexes[i]][columns[j]]);
                }
            }
        }
    }
    
    var maxRepetitions = 20;
    
    // The maxRepetitions argument is optional, and will be ignored unless using
    // SNMP verison 2c
    session.tableColumns (oid, columns, maxRepetitions, responseCb); 

    session.trap (typeOrOid, [varbinds], [agentAddrOrOptions], callback)

    trap()方法发送一个SNMP trap.typeOrOid参数可以是两种类型之一。

    typeOrOid参数可以是两种类型之一:snmp.TrapType对象中定义的常量之一(不包括snmp.TrapType.EnterpriseSpecific常量),或者一个OID字符串。

    对于SNMP版本1,当指定常量时,陷阱中会设置以下字段。

    • 企业字段设置为OID1.3.6.1.4.1
    • 企业字段设置为OID1.3.6.1.4.1
    • 特定陷阱字段设置为0。

    当指定了OID字符串时,会在陷阱中设置以下字段。

    • 从OID字符串中去掉最后的小数点,并设置在特定的陷阱字段中。
    • 其余的OID字符串在企业领域设置。
    • generic-trap字段设置为常数snmp.TrapType.EnterpriseSpecific

    在这两种情况下,陷阱PDU中的时间戳字段被设置为process.uptime()函数返回的值乘以100

    SNMP版本2c的消息与版本1相比有很大不同。2c版陷阱的格式要简单得多,只是一个varbinds的序列。陷阱消息中的第一个varbind是sysUptime.0的OID(1.3.6.1.6.3.1.1.4.1.0)。这个varbind的值将是process.uptime()函数返回的值乘以100(可以通过在可选的options参数中提供upTime来覆盖,如下文所述)。

    这之后会有第二个varbind的snmpTrapOID.0 OID (1.3.6.1.6.3.1.1.4.1.0)。这个值取决于typeOrOid参数。如果指定了一个常量,那么常量的陷阱OID将被用来作为varbinds的值,否则指定的OID字符串将被用来作为varbind的值。

    可选的varbinds参数是要包含在陷阱中的varbinds数组,默认为空数组[]

    可选的agentAddrOrOptions参数可以是两种类型之一,一种是用于填充SNMP版本1类型陷阱的agent-addr字段的IP地址,默认为127.0.0.1,或者是一个对象,可以包含以下项目。

    • agentAddr - 用于填充SNMP版本1类型陷阱的代理地址字段的IP地址,默认值为127.0.0.1
    • upTime - trap中sysUptime.0 OID(1.3.6.1.6.3.1.4.1.0)的值,默认为process.uptime()函数返回的值乘以100。

    注意当使用SNMP版本2c时,如果指定了agentAddr参数,则会被忽略,因为版本2c的陷阱信息没有agent-addr字段。

    一旦陷阱被发送或发生错误,"callback "函数将被调用。以下参数将被传递给callback函数。

    • error - Error类或子类的实例,如果没有发生错误,则为null

    以下示例使用SNMP版本1的陷阱向远程主机发送企业特定的陷阱,并在陷阱中包含sysName(1.3.6.1.2.1.1.5.0)varbind。在发送trap之前,agentAddr字段使用DNS计算出本地主机的主机名。

    var enterpriseOid = "1.3.6.1.4.1.2000.1"; // made up, but it may be valid
    
    var varbinds = [
        {
            oid: "1.3.6.1.2.1.1.5.0",
            type: snmp.ObjectType.OctetString,
            value: "host1"
        }
    ];
    
    dns.lookup (os.hostname (), function (error, agentAddress) {
        if (error) {
            console.error (error);
        } else {
            // Override sysUpTime, specfiying it as 10 seconds...
            var options = {agentAddr: agentAddress, upTime: 1000};
            session.trap (enterpriseOid, varbinds, agentAddress,
                    function (error) {
                if (error)
                    console.error (error);
            });
        }
    }); 

    下面的例子使用SNMP版本1的trap向远程主机发送一个通用的link-down trap,它不包括任何varbinds或指定agentAddr参数。

    session.trap (snmp.TrapType.LinkDown, function (error) {
        if (error)
            console.error (error);
    }); 

    以下示例使用SNMP版本2c trap向远程主机发送企业特定的trap,并包含两个企业特定的varbinds。

    var trapOid = "1.3.6.1.4.1.2000.1";
    
    var varbinds = [
        {
            oid: "1.3.6.1.4.1.2000.2",
            type: snmp.ObjectType.OctetString,
            value: "Hardware health status changed"
        },
        {
            oid: "1.3.6.1.4.1.2000.3",
            type: snmp.ObjectType.OctetString,
            value: "status-error"
        }
    ];
    
    // version 2c should have been specified when creating the session
    session.trap (trapOid, varbinds, function (error) {
        if (error)
            console.error (error);
    }); 

    session.walk (oid, [maxRepetitions], feedCallback, doneCallback)

    walk()方法获取MIB树中指定的OID之后所有OID的词法值。

    对于SNMP版本1,会重复调用get()方法,直到到达MIB树的末端。对于SNMP版本2c,会重复调用getBulk(),直到到达MIB树的末端。

    oid参数是一个OID字符串。当使用SNMP版本2c时,可选的maxRepetitions参数被传递给getBulk()请求。

    一旦获取了所有的OID值,这个方法将不会调用一个回调。相反,feedCallback函数将在每次从远程主机收到响应时被调用。以下参数将被传递给feedCallback函数。

    • varbinds - varbinds数组,至少包含一个varbind。

    当使用SNMP版本2c时,必须使用snmp.isVarbindError()函数检查每个varbind是否存在错误条件。

    一旦到达MIB树的终点,或者发生了错误,doneCallback函数将被调用。以下参数将被传递给doneCallback函数。

    • error - Error类或子类的实例,如果没有发生错误,则为null

    一旦doneCallback函数被调用,请求就完成了,feedCallback函数将不再被调用。

    如果feedCallback函数在调用时返回一个true值,则不再调用get()getBulk()方法,而调用doneCallback

    下面的例子从ifTable(1.3.6.1.2.1.2.2)OID开始走到MIB树的最后。

    var oid = "1.3.6.1.2.1.2.2";
    
    function doneCb (error) {
        if (error)
            console.error (error.toString ());
    }
    
    function feedCb (varbinds) {
        for (var i = 0; i < varbinds.length; i++) {
            if (snmp.isVarbindError (varbinds[i]))
                console.error (snmp.varbindError (varbinds[i]));
            else
                console.log (varbinds[i].oid + "|" + varbinds[i].value);
        }
    }
    
    var maxRepetitions = 20;
    
    // The maxRepetitions argument is optional, and will be ignored unless using
    // SNMP verison 2c
    session.walk (oid, maxRepetitions, feedCb, doneCb); 

    使用本模块 - 通知接收机

    RFC 3413对接收 "通知类 "PDU的SNMP应用进行了分类。通知包括SNMP陷阱和通知。该库能够接收所有类型的通知PDU。

    • Trap-PDU(原来的v1 trap PDU,现在被认为是obselete)。
    • "Trapv2-PDU"(未确认的通知)
    • "InformRequest-PDU"(与 "Trapv2-PDU "格式相同,但有信息确认)

    该库提供了一个用于接收SNMP通知的Receiver类。该模块输出createReceiver()函数,创建一个新的Receiver实例。

    接收器创建一个Authorizer实例来控制传入的访问。更多细节见下面的Authorizer模块部分。

    snmp.createReceiver (options, callback)

    createReceiver()函数实例化并返回一个Receiver类的实例。

    // Default options
    var options = {
        port: 162,
        disableAuthorization: false,
        accessControlModelType: snmp.AccessControlModelType.None,
        engineID: "8000B98380XXXXXXXXXXXX", // where the X's are random hex digits
        address: null
        transport: "udp4"
    };
    
    var callback = function (error, notification) {
        if ( error ) {
            console.error (error);
        } else {
            console.log (JSON.stringify(notification, null, 2));
        }
    };
    
    receiver = snmp.createReceiver (options, callback); 

    选项'和回调'参数是强制性的。options参数是一个对象,可以是空的,可以包含以下字段: * port - 侦听通知的端口 - 默认为162。

    • port--监听通知的端口--默认为162。请注意,在某些系统中,绑定到162端口需要接收器进程以管理权限运行。如果不可能,则选择一个大于1024的端口。
    • disableAuthorization--对所有收到的基于社区的通知以及对收到的基于用户的通知,如果没有消息认证或隐私(noAuthNoPriv),则禁用本地授权--默认为false。
    • engineID -- -- 用于SNMPv3通信的引擎ID,以十六进制字符串形式给出 -- -- 默认为系统生成的引擎ID,包含随机元素。
    • transport -- -- 要使用的传输系列 -- -- 默认为udp4
    • address -- -- 要绑定的IP地址 -- -- 默认为null,即绑定到所有IP地址。

    callback参数是一个回调函数,其形式为function (error, notification)。在发生错误时,"notification "参数被设置为 "null"。当成功接收到一个通知时,错误参数被设置为nullnotification参数被设置为一个对象,在pdu字段中包含通知PDU细节,在rinfo字段中包含发送方socket细节。例如:

    {
        "pdu": {
            "type": 166,
            "id": 45385686,
            "varbinds": [
                {
                    "oid": "1.3.6.1.2.1.1.3.0",
                    "type": 67,
                    "value": 5
                },
                {
                    "oid": "1.3.6.1.6.3.1.1.4.1.0",
                    "type": 6,
                    "value": "1.3.6.1.6.3.1.1.5.2"
                }
            ],
            "scoped": false
        },
        "rinfo": {
            "address": "127.0.0.1",
            "family": "IPv4",
            "port": 43162,
            "size": 72
        }
    } 

    receiver.getAuthorizer ()

    返回接收器的Authorizer实例,用于控制对接收器的访问。更多细节请参见 "Authorizer "部分。

    receiver.close ()

    关闭接收机的监听插座,结束接收机的操作。

    使用本模块 - SNMP代理

    SNMP代理响应与命令响应器应用相关的所有四个 "请求类 "PDU。

    • GetRequest - 请求完全匹配的OID实例。
    • GetNextRequest - 在MIB树中请求词法上的 "下一个 "OID实例。
    • GetBulkRequest - 请求MIB树中的一系列 "下一个 "OID实例。
    • SetRequest - 为指定的OID设置数值。

    代理发送GetResponse PDU到所有四种请求PDU类型,符合RFC 3416。

    代理--和通知接收方一样--维护一个Authorizer实例来控制对代理的访问,详细内容见下面的Authorizer模块部分。

    代理维护的中央数据结构是一个Mib实例,其API详见下面的Mib模块部分。代理允许通过API对MIB进行查询和操作,也允许通过SNMP接口与上述四个请求类PDU进行查询和操作。

    该代理还通过其单人Forwarder实例支持SNMP代理转发应用,这在下面的Forwarder模块部分有说明。

    snmp.createAgent (options, callback, mib)

    createAgent()函数实例化并返回一个Agent类的实例。

    // Default options
    var options = {
        port: 161,
        disableAuthorization: false,
        accessControlModelType: snmp.AccessControlModelType.None,
        engineID: "8000B98380XXXXXXXXXXXX", // where the X's are random hex digits
        address: null
        transport: "udp4"
    };
    
    var callback = function (error, data) {
        if ( error ) {
            console.error (error);
        } else {
            console.log (JSON.stringify(data, null, 2));
        }
    };
    
    agent = snmp.createAgent (options, callback); 

    选项'和回调'参数是强制性的。options参数是一个对象,可以是空的,可以包含以下字段。

    • port--代理要监听的端口--默认为161。请注意,在某些系统上绑定到161端口需要接收方进程以管理权限运行。如果无法做到这一点,则选择一个大于1024的端口。
    • disableAuthorization--对于收到的所有基于社区的通知和基于用户的通知,如果没有消息认证或隐私(noAuthNoPriv),则禁用本地授权--默认为false。
    • accessControlModelType -- -- 指定使用哪种访问控制模型。默认值为snmp.AccessControlModelType.None,但可以设置为snmp.AccessControlModelType.Simple,以获得更多的访问控制能力。更多信息请参见Authorization类描述。
    • engineID--用于SNMPv3通信的引擎ID,给定为十六进制字符串--默认为系统生成的引擎ID,包含随机元素。
    • transport -- -- 要使用的传输系列 -- -- 默认为udp4
    • address -- -- 要绑定的IP地址 -- -- 默认为null,即绑定到所有IP地址。

    mib参数是可选的,它设置了代理的单体Mib实例。如果不提供,代理会给自己创建一个新的空的Mib单体。如果提供,则需要按照下面的Mib模块部分来创建和填充Mib实例。

    agent.getAuthorizer ()

    返回代理的单人Authorizer实例,用于控制对代理的访问。更多细节请参见 "Authorizer "部分。

    agent.getMib ()

    返回Mib单例。

    agent.setMib (mib)

    将代理的单Mib实例设置为提供的实例。代理商放弃其现有的Mib实例。

    agent.getForwarder ()

    返回代理的单 "Forwarder "实例,该实例持有注册的代理列表,该代理指定基于上下文转发到远程主机。

    agent.close ()

    关闭代理的监听套接字,结束代理的操作。

    Authorizer Module

    接收器和代理都维护一个单例的 "Authorizer "实例,它负责维护SNMP社区的授权列表(针对v1和v2c通知)和SNMP用户的授权列表(针对v3通知)。这些列表用于授权接收方对通知的访问,并存储安全协议和密钥设置。RFC 3414将用户列表称为存储在接收器的 "本地配置数据库 "中的 "usmUserTable"。

    如果收到的v1或v2c通知中的社区不在接收器的社区授权列表中,接收器将不接受该通知,而是向提供的回调函数返回一个类RequestFailedError的错误。类似的,如果接收到一个v3通知,其用户名称不在接收者的用户授权列表中,接收者将返回一个RequestFailedError。如果在启动时为接收器提供了disableAuthorization选项,那么对于社区通知和noAuthNoPriv用户通知,这些本地授权列表检查将被禁用。请注意,即使有这个设置,用户列表仍然会对 authNoPriv 和 authPriv 通知进行检查,因为库仍然需要访问正确的密钥来进行消息认证和加密操作,而这些密钥是针对用户授权列表中的用户存储的。

    API允许对接收者/代理的社区授权和用户授权列表进行添加、查询和删除管理。

    对于代理来说,还有一个可选的访问控制检查,它可以根据代理提供的作为选项的AccessControlModelType来限制给定社区或用户的访问。默认的模型类型是snmp.AccessControlModelType.None,这意味着--在前面几段描述的授权列表检查之后,没有进一步的访问控制限制,即所有请求都被代理授予访问权。可以选择第二个访问控制模型类型snmp.AccessControlModelType.Simple,它创建了一个SimpleAccessControlModel对象,该对象可以被操作,以指定社区或用户对代理信息具有三个级别的访问权限之一。

    • 只读
    • 读写器

    关于如何使用 "SimpleAccessControlModel "类配置访问的更多信息,将在下面对该类的描述中提供。

    授权器实例可以通过使用getAuthorizer()调用获得,对于接收方和代理方来说都是如此。例如:

    receiver.getAuthorizer ().getCommunities (); 

    authorizer.addCommunity (community)

    在接收者的社区授权列表中添加一个社区字符串。如果社区已经在列表中,则不做任何操作,确保列表中任何给定的社区字符串只出现一次。

    authorizer.getCommunity (community)

    如果接收者的社区授权列表中存储了一个社区字符串,则返回 "null",否则返回 "null"。

    authorizer.getCommunities ()

    返回接收者的社区授权列表。

    authorizer.deleteCommunity (community)

    从接收者的社区授权列表中删除一个社区字符串。如果社区不在列表中,则不做任何操作。

    authorizer.addUser (user)

    在接收方的用户授权列表中添加一个用户。如果列表中存在同名用户,则该调用将删除现有用户,并以提供的用户取而代之,确保列表中只存在一个同名用户。用户对象的格式与session.createV3Session()调用的格式相同。

    var user = {
        name: "elsa"
        level: snmp.SecurityLevel.authPriv,
        authProtocol: snmp.AuthProtocols.sha,
        authKey: "imlettingitgo",
        privProtocol: snmp.PrivProtocols.des,
        privKey: "intotheunknown"
    };
    
    receiver.getAuthorizer ().addUser (elsa); 

    authorizer.getUser (userName)

    如果接收者的用户授权列表中存储了一个使用所提供名字的用户,则返回一个用户对象,否则返回null

    authorizer.getUsers ()

    返回接收方的用户授权列表。

    authorizer.deleteUser (userName)

    从接收方的用户授权列表中删除一个用户。如果提供的用户名称不在列表中,则不做任何操作。

    authorizer.getAccessControlModelType ()

    返回该授权器的snmp.AccessControlModelType,它是其中之一。

    • snmp.AccessControlModelType.None
    • snmp.AccessControlModelType.Simple

    authorizer.getAccessControlModel ()

    返回访问控制模型对象。

    • for a type of snmp.AccessControlModelType.None - returns null (as the access control check returns positive every time)
    • for a type of snmp.AccessControlModelType.Simple - returns a SimpleAccessControlModel object

    Simple Access Control Model

    SimpleAccessControlModel'类可以选择作为Agent'使用的访问控制模型。SimpleAccessControlModel为给定的社区或用户提供基本的三级访问控制。访问级别由snmp.AccessLevel常量指定。

    • snmp.AccessLevel.None--不授予社区或用户任何访问权。
    • snmp.AccessLevel.ReadOnly -- -- 允许社区或用户访问Get、GetNext和GetBulk请求,但不允许访问Set请求。
    • snmp.AccessLevel.ReadWrite -- -- 允许社区或用户访问Get、GetNext、GetBulk和Set请求。

    SimpleAccessControlModel不是通过直接的API调用创建的,而是由AgentAuthorizer单人在内部创建的。所以可以用以下方法访问代理的访问控制模型。

    var acm = agent.getAuthorizer ().getAccessControlModel (); 

    请注意,本节中任何 API 调用中使用的任何社区或用户都必须首先在代理的 "授权者 "中创建,否则代理将无法通过授权者执行的初始社区/用户列表检查。

    当使用简单访问控制模型时,Authorizer中新创建的社区或用户的默认访问级别是只读。

    Example use:

    var agent = snmp.createAgent({
        accessControlModelType: snmp.AccessControlModelType.Simple
    }, function (error, data) {
        // null callback for example brevity
    });
    var authorizer = agent.getAuthorizer ();
    authorizer.addCommunity ("public");
    authorizer.addCommunity ("private");
    authorizer.addUser ({
        name: "fred",
        level: snmp.SecurityLevel.noAuthNoPriv
    });
    var acm = authorizer.getAccessControlModel ();
    // Since read-only is the default, explicitly setting read-only access is not required - just shown here as an example
    acm.setCommunityAccess ("public", snmp.AccessLevel.ReadOnly);
    acm.setCommunityAccess ("private", snmp.AccessLevel.ReadWrite);
    acm.setUserAccess ("fred", snmp.AccessLevel.ReadWrite); 

    simpleAccessControlModel.setCommunityAccess (community, accessLevel)

    授予给定的社区给定的访问级别。

    simpleAccessControlModel.removeCommunityAccess (community)

    删除指定社区的所有访问。

    simpleAccessControlModel.getCommunityAccessLevel (community)

    返回指定社区的访问级别。

    simpleAccessControlModel.getCommunitiesAccess ()

    返回该访问控制模型定义的所有社区访问控制条目的列表。

    simpleAccessControlModel.setUserAccess (userName, accessLevel)

    给予用户指定的访问级别。

    simpleAccessControlModel.removeUserAccess (userName)

    删除指定用户的所有访问权限。

    simpleAccessControlModel.getUserAccessLevel (userName)

    返回指定用户的访问级别。

    simpleAccessControlModel.getUsersAccess ()

    返回该访问控制模型定义的所有用户访问控制项的列表。

    Mib Module

    代理'实例在创建时,又创建了Mib'类的一个实例。一个代理总是有且只有一个Mib实例。通过agent.getMib()调用来访问代理的Mib实例。

    MIB是一个树状结构,它保存着管理信息。信息在树中由一系列整数 "寻址",这些整数从树的根部向下形成一个对象ID(OID)。

    在MIB中,只有两种数据结构可以保存数据。

    标量数据--标量变量存储在MIB树的某个节点上,变量的值是标量变量节点的单个子节点,地址总是 "0"。例如,sysDescr标量变量的地址为 "1.3.6.1.2.1.1.1"。sysDescr变量的值存储在 "1.3.6.1.2.1.1.0"

    
    ```
    1.3.6.1.2.1.1.1 <= sysDescr (标量变量)
    1.3.6.1.2.1.1.0 = OctetString: MyAwesomeHost <= sysDescr.0 (标量变量值) 
    ```
    

    数据--SNMP表以列和行的形式存储数据。通常情况下,如果一个表存储在MIB中的某个节点上,那么在表OID的正下方有一个地址为 1 条目 对象。在 条目 的正下方是一个列的列表,这些列的编号通常是从 1 往上的。在每一列的下面是一系列的行。在最简单的情况下,一行的 索引是表中的一列,但行索引可以是一系列的列,也可以是给出多个整数的列(如一个IPv4地址的索引有四个整数),或者两者都有。下面是ifTable中部分SNMP表的层次结构的例子。

    1.3.6.1.2.1.2.2 <= ifTable (table)
    1.3.6.1.2.1.2.2.1 <= ifEntry (表项)
    1.3.6.1.2.1.2.2.1.1 <= ifIndex (第1栏)
    1.3.6.1.2.1.2.1.1 = Integer: 1 <= ifIndex row 1 value = 1。
    1.3.6.1.2.1.2.2.1.1.2 = Integer: 2 <= ifIndex row 2 value = 2。

    在创建时,"Agent "实例会创建一个 "Mib "模块的单人实例。然后,您可以向代理的Mib实例注册一个 "提供者",它为标量数据实例或表提供一个接口。

    var myScalarProvider = {
        name: "sysDescr",
        type: snmp.MibProviderType.Scalar,
        oid: "1.3.6.1.2.1.1.1",
        scalarType: snmp.ObjectType.OctetString,
        handler: function (mibRequest) {
           // e.g. can update the MIB data before responding to the request here
           mibRequest.done ();
        }
    };
    var mib = agent.getMib ();
    mib.registerProvider (myScalarProvider);
    mib.setScalarValue ("sysDescr", "MyAwesomeHost"); 

    这段代码首先给出了标量 "提供者 "的定义。在mib.registerProvider()部分对这些字段做了进一步的解释。重要的是,name字段是提供者的唯一标识符,在后续的API调用中用于选择特定的提供者。

    registerProvider()调用将提供者添加到MIB持有的提供者列表中。请注意,这个调用不会将 "oid "节点添加到MIB树中。第一次调用setScalarValue()将把实例OID "1.3.6.1.2.1.1.1.0 "连同其值一起添加到MIB树中。

    此时,当通过SNMP查询实例OID "1.3.6.1.1.2.1.1.1.0 "时,代理将提供该MIB节点的值。

    一个表提供者也有类似的定义。

    var myTableProvider = {
        name: "smallIfTable",
        type: snmp.MibProviderType.Table,
        oid: "1.3.6.1.2.1.2.2.1",
        tableColumns: [
            {
                number: 1,
                name: "ifIndex",
                type: snmp.ObjectType.Integer
            },
            {
                number: 2,
                name: "ifDescr",
                type: snmp.ObjectType.OctetString
            },
            {
                number: 3,
                name: "ifType",
                type: snmp.ObjectType.Integer,
                constraints: {
                    enumeration: {
                        "1": "goodif",
                        "2": "averageif",
                        "3": "badif"
                    }
                }
            }
        ],
        tableIndex: [
            {
                columnName: "ifIndex"
            }
        ]
    };
    var mib = agent.getMib ();
    mib.registerProvider (myTableProvider);
    mib.addTableRow ("smallIfTable", [1, "eth0", 6]); 

    在这里,提供者的定义需要两个添加字段。tableColumns表示列的定义,tableIndex表示用于行索引的列。在本例中,tableIndexifIndex列。mib.registerProvider()部分有关于构成提供者定义的字段的进一步细节。

    oid必须是 "表条目 "节点的节点,而不是它的父 "表 "节点,例如对于ifTable,提供者中的oid是 "1.3.6.1.2.1.2.2.1"(ifEntry的OID)。

    请注意,在这个特殊的例子中,没有handler回调函数,所以任何交互都是直接在SNMP请求和MIB值之间进行,没有其他干预。

    snmp.createMib ()

    createMib()函数实例化并返回一个Mib类的实例。新的Mib没有任何节点(除了一个根节点),也没有任何注册的提供者。

    请注意,这只适用于代理,而不是AgentX子代理。由于代理在创建时就会实例化一个Mib实例,所以在很多情况下不需要这个调用。有两种情况可能会用到它。

    • 在创建Agent实例之前,你想用提供者和标量/表格数据预先填充一个Mib实例。
    • 你想把一个代理的现有Mib实例换成一个全新的实例。

    mib.registerProvider (definition)

    在MIB中注册一个提供者定义。不向MIB树添加任何内容。

    提供者定义有以下几个字段: name(强制性的) - 提供者的名称。

    • name(强制) - 提供方的名称,它是获取和设置值时引用提供方的唯一键。
    • type(强制性) - 必须是snmp.MibProviderType.Scalarsnmp.MibProviderType.Table(强制性)
    • oid(必须填写) -提供者在MIB树中注册的OID。请注意,这不是的 "实例节点"(".0 "节点),而是它上面的节点。在这种情况下,提供者在 "1.3.6.1.2.1.1.1 "处注册,以提供 "1.3.6.1.2.1.1.0 "处的值。
    • scalarType(标量类型必须填写) -只与标量提供者类型相关,这给出了变量的类型,从snmp.ObjectType中选择。
    • tableColumns(表类型必须填写) -- -- 提供表的任何列定义对象数组。每个列对象必须有一个独特的数字'、名称'和snmp.ObjectType'的类型'。类型为ObjectType.Integer的列对象可以选择性地包含一个constraints对象,其格式和含义与在单个标量提供者上定义的对象相同(具体内容见下文constraints)。
    • tableIndex(表类型可选) -给出一个用于行索引的索引入口对象数组。对于单列索引使用单元素数组,对于复合索引使用多个值。一个索引条目对象有一个columnName字段,如果条目在另一个提供者的表中,那么包括一个foreign字段,写上外表提供者的名称。如果没有tableAugments字段,tableIndex是必须的。
    • tableAugments(表类型可选) -给出本表 "增强 "的另一个注册提供者的名称。这意味着索引信息是从给定提供者的表中获取的,并且不存在于本地表的列定义中。如果tableIndex字段不存在,tableAugments是必须的,即tableIndextableAugments中的一个需要存在,才能定义表的索引。
    • handler(optional) - 一个可选的回调函数,在向MIB提出请求之前被调用。这可以更新这个提供者处理的MIB值。如果没有给定,那么这些值将被简单地从MIB中返回(或设置),而不需要任何其他处理。回调函数需要一个MibRequest实例,它有一个done()函数。当处理完请求时,必须调用这个函数。MibRequest也有一个oid字段,写着被操作的实例OID,还有一个operation字段,写着来自snmp.PduType的请求类型。如果 "MibRequest "是针对 "SetRequest "PDU的,那么变量 "setValue "和 "setType "就包含了 "SetRequest "varbind中接收到的值和类型。
    • constraints(对于标量类型是可选的) - 一个可选的对象,用于指定基于整数的枚举类型的约束。目前唯一支持的约束是enumeration对象,它将整数映射到它们的命名类型,以捕获RFC 2578第7.1.1节中描述的 "命名数枚举"。任何SetRequest协议操作都会根据定义的约束条件进行检查,如果SetRequest中的值会违反约束条件,例如该值不是定义的枚举的成员,则不会采取行动。请注意,表列可以以相同的方式指定这样的 "约束",只是这些约束存储在每个列的列对象定义下。

    在向MIB注册提供者后,在其他API调用中,提供者由其名称引用。

    虽然这个调用将提供者注册到MIB,但它不会改变MIB树。

    mib.registerProviders ( [definitions] )

    一次调用注册提供者数组的方便方法。简单地调用 "registerProvider() "来注册数组中的每个提供者定义。

    mib.unregisterProvider (name)

    从MIB中取消注册一个提供者。这也将从提供者的oid节点向下删除所有MIB节点。它还将对MIB树上游的任何内部MIB节点进行修剪,这些节点仅在MIB树到达提供者oid节点时存在。

    mib.getProviders ()

    返回在MIB中注册的提供者定义的对象,按提供者名称索引。

    mib.getProvider (name)

    返回给定名称的单个注册提供者对象。

    mib.getScalarValue (scalarProviderName)

    从一个标量提供者那里检索值。

    mib.setScalarValue (scalarProviderName, value)

    设置标量提供者的值。如果这是该提供者向MIB注册后第一次设置标量,那么它还会将实例(".0")节点和所有所需的祖先添加到MIB树中。

    mib.addTableRow (tableProviderName, row)

    将表行--以值数组的形式--添加到表提供者。如果表是空的,则在添加值行之前,实例化提供者的oid节点和祖先,即其列。请注意,该行是一个按照表列顺序排列的元素数组。如果表有任何外来索引列(即那些不属于本表的索引列),那么这些列的值必须按照它们在MIB INDEX子句中出现的顺序,包含在行数组的开头。

    mib.getTableColumnDefinitions (tableProviderName)

    返回提供者的列定义对象列表。

    mib.getTableCells (tableProviderName, byRow, includeInstances)

    返回表格数据的二维数组。如果byRow为false(默认),那么表格数据是以列数组列表的形式给出的,即按列给出。如果byRowtrue,那么数据就是一个行数组的列表。如果includeInstancestrue,那么,对于列视图,将有一个额外的第一列的实例索引信息。如果行视图中includeInstancestrue,那么在每一行的开始会有一个附加元素,包含索引信息。

    mib.getTableColumnCells (tableProviderName, columnNumber, includeInstances)

    返回给定列号的单列表数据。如果 "includeInstances "为 "true",则返回两个数组:第一个数组包含实例索引信息,第二个数组包含列数据。

    mib.getTableRowCells (tableProviderName, rowIndex)

    返回给定行索引的单行表数据。行索引是一个由索引值组成的数组,从紧挨着列下的节点到行实例末尾的节点,这将是MIB树中的一个叶子节点。最终,非整数值需要转换为整数序列,构成OID的实例部分。下面是将索引值转换为行实例OID序列的细节。

    • ObjectType.Integer - 单个整数。
    • ObjectType.OctetString - 一个ASCII值的整数序列。
    • ObjectType.OID - OID中的确切整数序列。
    • ObjectType.IpAddress - IP地址中四个整数的序列。

    mib.getTableSingleCell (tableProviderName, columnIndex, rowIndex)

    返回指定列和行的单个单元格值。行索引数组的指定方式与getTableRowCells()调用相同。

    mib.setTableSingleCell (tableProviderName, columnIndex, rowIndex, value)

    在指定的列和行设置一个单元格值。行索引数组的指定方式与getTableRowCells()调用相同。

    mib.deleteTableRow (tableProviderName, rowIndex)

    在指定的行索引处删除表的一行。行索引数组的指定方式与getTableRowCells()调用相同。如果这是表的最后一行,表就会从MIB中被修剪掉,尽管提供者仍然在MIB中注册。这意味着,在增加另一行时,该表将被再次实例化。

    mib.dump (options)

    以文本格式转储MIB。options对象通过这些选项字段控制转储的显示(所有选项都是布尔值,默认为true)。

    • leavesOnly--不单独显示内部节点--只显示叶子节点的前缀部分(实例节点)。
    • showProviders -- -- 显示提供者与MIB相连的节点。
    • "showTypes" -- -- 显示实例价值类型。
    • showValues - 显示实例值。

    For example:

    mib.dump (); 

    produces this sort of output:

    1.3.6.1.2.1.1.1 [Scalar: sysDescr]
    1.3.6.1.2.1.1.1.0 = OctetString: Rage inside the machine!
    1.3.6.1.2.1.2.2.1 [Table: ifTable]
    1.3.6.1.2.1.2.2.1.1.1 = Integer: 1
    1.3.6.1.2.1.2.2.1.1.2 = Integer: 2
    1.3.6.1.2.1.2.2.1.2.1 = OctetString: lo
    1.3.6.1.2.1.2.2.1.2.2 = OctetString: eth0
    1.3.6.1.2.1.2.2.1.3.1 = Integer: 24
    1.3.6.1.2.1.2.2.1.3.2 = Integer: 6 

    Using This Module: Module Store

    该库支持MIB解析,为ModuleStore实例提供了一个接口,您可以从文件中加载MIB模块,并获取由此产生的JSON MIB模块表示。

    此外,一旦一个MIB被加载到模块存储中,你就可以生成一个MIB "提供者 "定义的列表,一个 "代理 "可以注册(更多细节请参见 "代理 "文档),这样你就可以马上开始操作你的MIB文件中定义的所有值。

    // Create a module store, load a MIB module, and fetch its JSON representation
    var store = snmp.createModuleStore ();
    store.loadFromFile ("/path/to/your/mibs/SNMPv2-MIB.mib");
    var jsonModule = store.getModule ("SNMPv2-MIB");
    
    // Fetch MIB providers, create an agent, and register the providers with your agent
    var providers = store.getProvidersForModule ("SNMPv2-MIB");
    // Not recommended - but authorization and callback turned off for example brevity
    var agent = snmp.createAgent ({disableAuthorization: true}, function (error, data) {});
    var mib = agent.getMib ();
    mib.registerProviders (providers);
    
    // Start manipulating the MIB through the registered providers using the `Mib` API calls
    mib.setScalarValue ("sysDescr", "The most powerful system you can think of");
    mib.setScalarValue ("sysName", "multiplied-by-six");
    mib.addTableRow ("sysOREntry", [1, "1.3.6.1.4.1.47491.42.43.44.45", "I've dreamed up this MIB", 20]);
    
    // Then hit those bad boys with your favourite SNMP tools (or library ;-), e.g.
    snmpwalk -v 2c -c public localhost 1.3.6.1 

    这意味着您可以用最少的模板代码直接实现您的MIB功能。

    snmp.createModuleStore ()

    创建一个新的ModuleStore实例,该实例预装了一些 "基础 "MIB模块,这些模块提供了其他MIB模块常用的MIB定义("导入")。预装的 "基础 "模块列表如下:

    • RFC1155-SMI
    • RFC1158-MIB
    • RFC-1212
    • RFC1213-MIB
    • SNMPv2-SMI
    • SNMPv2-CONF
    • SNMPv2-TC
    • SNMPv2-MIB

    store.loadFromFile (fileName)

    将给定文件中的所有MIB模块加载到模块存储中。按照惯例,每个文件中通常只有一个MIB模块,但一个文件中可以存储多个模块定义。然后,加载的MIB模块被这个API用它们的MIB模块名而不是源文件名来引用。MIB模块名是MIB文件中DEFINITIONS ::= BEGIN前面的名称,通常是MIB文件中最先出现的东西。

    请注意,如果您的MIB依赖于("导入")其他MIB文件中的定义,则必须首先加载这些定义,例如,流行的IF-MIB使用了来自IANAifType-MIB的定义,因此必须首先加载。这些依赖关系列在MIB模块的IMPORTS部分,通常在MIB文件的顶部。预先加载的 "基础 "MIB模块包含了许多常用的导入。

    store.getModule (moduleName)

    以JSON对象的形式从存储中检索命名的MIB模块。

    store.getModules (includeBase)

    从store中检索所有的MIB模块,如果includeBaseboolean被设置为true,那么基本的MIB模块就会被包含在列表中。如果 "includeBase "布尔值被设置为true,那么基本的MIB模块就被包含在列表中。模块作为一个单一的JSON "对象中的对象 "返回,以模块名称为键,其值是整个JSON模块的表示。

    store.getModuleNames (includeBase)

    检索商店中加载的所有MIB模块名称的列表。如果 "includeBase "布尔值被设置为true,那么MIB模块的基本名称就会被包含在列表中。

    store.getProvidersForModule (moduleName)

    返回一个Mib"提供者 "定义的数组,该数组对应于命名的MIB模块中包含的所有标量和表实例对象。然后,通过使用agent.getMib().registerProviders()调用,提供者定义列表就可以被注册到代理的MIB中。

    Forwarder Module

    代理'实例在创建后,又会创建Forwarder'类的实例。没有直接的API调用来创建 "Forwarder "实例;这种创建是代理的责任。一个代理总是只有一个Forwarder实例。代理的Forwarder实例是通过agent.getForwarder()调用来访问的。

    Forwader就是RFC 3413所说的 "代理转发应用"。它维护着一个 "代理 "条目列表,每个条目都配置了一个命名的SNMPv3上下文名称,以使用户凭证能够访问给定的目标主机。Forwarder只支持SNMPv3会话的代理。

    var forwarder = agent.getForwarder ();
    forwarder.addProxy({
        context: "slatescontext",
        host: "bedrock",
        user: {
            name: "slate",
            level: snmp.SecurityLevel.authNoPriv,
            authProtocol: snmp.AuthProtocols.sha,
            authKey: "quarryandgravel"
        },
    }); 

    现在,使用所提供的 "slatescontext "上下文向代理发出的请求将被转发到主机 "bedrock",并使用所提供的用户 "slate "的证书。

    你可以使用本地代理用户(与代理的Authorizer实例一起添加)查询代理。假设你的代理运行在localhost,161端口,你可以添加本地用户 "fred",然后用新的 "fred "用户访问代理。

    var authorizer = agent.getAuthorizer();
    authorizer.addUser ({
        name: "fred",
        level: snmp.SecurityLevel.noAuthNoPriv
    });
    
    // Test access using Net-SNMP tools (-n is the context option):
    
    snmpget -v 3 -u fred -l noAuthNoPriv -n slatescontext localhost 1.3.6.1.2.1.1.1.0 

    根据代理的定义,该代理将请求传递给主机 "bedrock"。

    forwarder.addProxy (proxy)

    为转发者添加一个新的代理。代理是一个包含以下字段的对象。

    • context(强制) - 这个代理条目的SNMPv3上下文名称。这是代理条目的唯一键,即不能有两个代理条目的上下文名称相同。
    • transport(可选) - 指定到达远程目标的传输。可以是udp4udp6,默认为udp4
    • target(强制) - 接收代理请求的远程主机。
    • port(可选) - 远程主机上SNMP代理的端口。默认值为161。
    • user(必填) - SNMPv3用户。用户的格式在createV3Session()调用文档中描述。

    forwarder.deleteProxy (context)

    从转发者中删除给定上下文的代理。

    forwarder.getProxy (context)

    返回给定上下文的转发者的代理。

    forwarder.getProxies ()

    返回一个包含所有注册代理的列表的对象,按上下文名称键入。

    forwarder.dumpProxies ()

    打印所有代理定义的转储到控制台。

    Using This Module: AgentX Subagent

    AgentX子代理实现了RFC 2741中指定的功能,成为AgentX "主代理 "的一个 "子代理"。AgentX的目标是通过一个单独的 "子代理 "来扩展现有的 "主代理 "SNMP代理的功能,注册它想为主代理管理的MIB树的部分。

    除了两个 "管理 "PDU类型外,AgentX子代理支持生成所有的PDU类型,所有的PDU都是从子代理发送到主代理的。

    • Open PDU--打开与主代理的新会话
    • 关闭PDU - 关闭与主代理的现有会话。
    • 注册PDU -- -- 注册一个MIB区域,以便与主代理进行控制。
    • 解除注册PDU -- -- 解除以前向主代理注册的MIB区域的注册。
    • Notify PDU - 向主代理发送通知。
    • Ping PDU - 发送 "Ping "以确认主代理仍然可用。
    • AddAgentCaps PDU - 在主代理的sysORTable中添加代理功能。
    • RemoveAgentCaps PDU--从主代理的sysORTable中删除以前添加的代理功能。

    两种不支持的 "管理 "PDU类型是: * IndexAllocate PDU - 请求从索引由主代理管理的表分配索引。

    • IndexAllocate PDU--请求分配由主代理管理索引的表的索引。
    • IndexDeallocate PDU -- -- 请求从主代理的表中重新分配以前分配的索引。

    这些都是不支持的,因为它们不适合当前的MIB提供者注册模型,该模型只支持注册标量和整个表格。将来可以通过进一步概括注册模型来支持表行注册来支持这些。

    子代理响应所有与命令响应者应用相关的 "请求处理 "PDU类型,这些类型是从主代理接收的。

    • Get PDU--请求完全匹配的OID实例。
    • GetNext PDU--请求MIB树中的词法 "下一个 "OID实例。
    • GetBulk PDU -- -- 请求MIB树中一系列的 "下一个 "OID实例。
    • TestSet PDU - 测试将作为单一事务提交的 "集 "操作列表。
    • CommitSet PDU - 将一系列 "set "操作作为单一事务提交。
    • UndoSet PDU -- -- 将 "set "操作列表作为单一事务撤销。
    • CleanupSet PDU -- -- 结束 "设置 "事务。

    根据 RFC 2741,除了 CleanupSet PDU 外,所有这些都会返回一个 Response PDU 给主代理。

    与SNMP代理一样,AgentX子代理维护的是一个Mib实例,其API在上面的Mib模块部分有详细介绍。子代理允许通过API查询和操作MIB,也允许通过AgentX接口查询和操作上述 "请求处理 "PDU(当主代理调用其SNMP接口时,主代理会产生)。

    重要的是,MIB提供者是使用子代理的subagent.registerProvider()调用来注册的(如下所述),而不是使用subagent.getMib().registerProvider(),因为子代理既需要在其内部Mib对象上注册提供者,_又需要为提供者的MIB区域发送一个Register PDU给主代理。如果直接在MIB对象上注册提供者,则跳过后一步_。

    snmp.createSubagent (options)

    createSubagent()函数实例化并返回一个Subagent类的实例。

    // Default options
    var options = {
        master: localhost
        masterPort: 705,
        timeout: 0,
        description: "Node net-snmp AgentX sub-agent",
    };
    
    subagent = snmp.createSubagent (options); 

    options参数是一个强制性对象,可能为空,可包含以下字段。

    • master - 主代理的主机名或IP地址,子代理连接到该主机。
    • masterPort--子代理连接到主代理的TCP端口,默认为705。
    • timeout - 设置主代理的会话范围超时 - 默认值为0,即没有设置会话范围超时。
    • description--子代理的文字描述。

    subagent.getMib ()

    返回代理的单Mib实例。 在创建子代理时自动创建,并保存子代理的所有管理数据。

    subagent.open (callback)

    向主代理发送 "Open "PDU以打开一个新的会话,并在主代理响应时调用回调。

    subagent.close (callback)

    向主代理发送 "关闭 "PDU,关闭子代理与主代理的会话,在主代理响应时调用回调。

    subagent.registerProvider (provider, callback)

    关于提供者的定义,请参见MibregisterProvider()调用。对于这个调用,provider对象的格式和意义是一样的。这将向主站发送一个RegisterPDU来注册MIB的一个区域,主站将为这个区域向子代理发送 "请求处理 "PDU。所提供的 "回调 "只使用一次,在接收到主站对 "Register "PDU的后续 "Response "PDU时使用。这不能与提供者定义中的可选回调 "handler "相混淆,后者对子代理收到的任何 "请求处理 "PDU都会被调用,用于注册MIB区域中的MIB对象。

    subagent.unregisterProvider (name, callback)

    通过提供的提供者的名称来解除对以前注册的MIB区域的注册。向主代理发送一个 "解除注册 "PDU来完成这项工作。所提供的 "回调 "只使用一次,在收到主代理的后续 "响应 "PDU时才会使用 "Unregister "PDU。

    subagent.registerProviders ( [definitions], callback )

    一次调用注册提供者数组的方便方法。简单地对数组中的每个提供者定义调用registerProvider()。对于每个注册的提供者,"回调 "函数被调用一次。

    subagent.getProviders ()

    返回在MIB中注册的提供者定义的对象,按提供者名称索引。

    subagent.getProvider (name)

    Returns a single registered provider object for the given name.

    subagent.addAgentCaps (oid, descr, callback)

    将一个由oiddescr组成的代理能力添加到主代理的sysORTable中。向主代理发送一个 "AddAgentCaps "PDU来完成这项工作。当收到主代理发送的后续 "Response "PDU到 "AddAgentCaps "PDU时,会调用所提供的 "callback"。

    subagent.removeAgentCaps (oid, callback)

    从主代理的sysORTable中删除之前添加的能力。向主代理发送一个 "RemoveAgentCaps "PDU来完成这个操作。当收到主代理发送的后续 "Response "PDU到 "RemoveAgentCaps "PDU时,会调用提供的 "callback"。

    subagent.notify (typeOrOid, varbinds, callback)

    使用NotifyPDU向主代理发送通知。通知的形式与上面session.inform()部分以及RFC 2741第6.2.10节中概述的相同,即创建两个varbinds,始终包含在通知中。

    • sysUptime.0(1.3.6.1.2.1.1.3.0)--包含子代理的运行时间。
    • snmpTrapOID.0 (1.3.6.1.6.3.1.1.4.1.0) -- -- 包含所提供的OID(或所提供的snmp.TrapType值);

    可选的 "varbinds "列表是一个附加的varbind对象列表,用于附加到上述两个varbinds。所提供的callback在收到主站随后的ResponsePDU和NotifyPDU时被调用。

    subagent.ping (callback)

    使用 "Ping "PDU向主代理发送 "ping",以确认主代理仍在响应。提供的 "回调 "在接收到主代理对 "Ping "PDU的后续 "Response "PDU时被调用。

    Example Programs

    示例程序包含在模块的example目录下。

    变化

    版本1.0.0 - 14/01/2013

    初始版本仅包括SNMP版本1的支持

    版本1.1.0 - 20/01/2013

    实施SNMP 2c版本支持

    版本1.1.1-21/01/2013

    在调用require()的例子中使用了正确的名称来包含这个模块。

    版本1.1.2 - 22/01/2013

    实现子树()、表()和走()方法。
    支持IPv6(为createSession()函数添加了传输选项)
    重新编排README.md中的一些方法。

    版本 1.1.3 - 27/01/2013

    修正README.md中的一些错别字和语法错误。
    示例的snmp-table程序在使用信息中出现了snmp-subtree。
    实现snmp-tail程序示例,不断轮询OIDs值
    在README.md中添加关于通过返回true来停止walk()和subtree()方法的说明。

    版本1.1.4 - 29/01/2013

    修正README.md中 "NPM "一词的错误用法,应为 "npm"

    版本1.1.5 - 05/02/2013

    没有使用createSession()的传输选项。

    版本1.1.6 - 12/04/2013

    实现tableColumns()方法
    添加了示例程序snmp-table-columns.js。
    表参数在table()回调中的名称正确。
    OID比较性能略有提高

    版本1.1.7 - 11/05/2013

    使用MIT许可证而不是GPL

    版本1.1.8 - 22/06/2013

    添加了示例程序cisco-device-inventory.js。
    接收陷阱失败。TypeError: 当使用SNMP版本2c发送陷阱时,值超出了范围。

    版本1.1.9 - 03/11/2013

    纠正了README.md文件中一些方法中名为requestCallback的参数,这些参数应该是feedCallback。
    Null类型用于值为0的varbinds。
    在README.md文件中,将snmp.Type的实例更正为snmp.ObjectType。

    版本1.1.10 - 01/12/2013

    在发送()方法中的dgram.send()回调中的错误处理程序正在从错误参数中创建一个新的Error类实例,但它已经是Error类的一个实例了(感谢Ray Solomon)
    在该模块导出的错误类中添加堆栈痕迹(感谢Ray Solomon)。
    允许用户在创建会话时指定0次重试(感谢Ray Solomon)。
    更新我们在 README.md 文件的标准符合性部分所遵守的 SNMP 版本 1 相关 RFC 的列表。

    版本1.1.11 - 27/12/2013

    在Session类的createSession()方法中增加sourceAddress和sourcePort可选选项,可以用来控制消息从哪个IP地址和端口发送。
    允许用户为SNMP traps指定sysUpTime,并通知用户。

    版本1.1.12 - 02/04/2014

    当在选项对象中传递给trap()方法时,不会使用agentAddr属性。
    1.1.13版本--2014年8月12日
    没有捕获从dgram.createSocket()函数返回的UDP套接字的错误事件。
    一些请求方法没有复制参数,导致有时出现意外行为。
    在一个SNMP会话中对所有请求使用一个UDP套接字。
    在Session.send()方法的定时器回调中使用try/catch块。
    会话现在可以发出一个错误事件来捕获会话底层UDP套接字中的错误。
    会话现在可以发出一个关闭事件,以捕获来自会话底层UDP套接字的关闭事件,这将导致所有未完成的请求被取消。
    在Session中添加了一个close()方法来关闭一个会话的底层UDP套接字,从而产生一个关闭事件。
    在解析响应消息时,有符号的整数会被当作无符号的整数来处理
    1.1.14版本-2015年9月22日
    GitHub上的主机仓库

    版本1.1.15-2016年2月8日

    当解析无效响应时,消息解析中的异常不会中断响应处理。
    在响应处理过程中处理错误时,在调用req.responseCb时错误地传递了req对象。
    1.1.16版本-2016年2月29日
    解决用户在Mocha测试套件中发现的一些问题。
    1.1.17版-2016年3月21日
    在Session对象构造函数中正确引用了不存在的req变量(应该是this)。

    版本1.1.18-15/05/2015

    修正snmp.createSession()函数的参数号和名称。
    为README.md文件中的一个例子添加缺失的括号。
    1.1.19版-2016年8月26日
    删除64位整数检查,以确保发送和接收的消息中最多包含8个字节。

    版本1.2.0 - 22/07/2017

    将asn1依赖性改为asn1-ber。
    1.2.1版-2018年2月11日
    增加对 16 位 id 的支持,以帮助与旧设备进行互操作(在 createSession() 函数中增加了 idBitsSize 选项。
    在README.md中添加说明,即在使用完毕后应关闭会话。

    版本1.2.3 - 06/06/2018

    设置NoSpaceships Ltd为所有者和维护者。

    版本1.2.4 - 07/06/2018

    删除README.md中多余的部分。

    版本2.0.0 - 16/01/2020

    增加SNMPv3支持

    2.1.0版--2020年1月16日。

    添加陷阱并通知接收方

    2.1.1版-2020年1月17日

    增加CONTRIBUTING.md准则。

    2.1.2版-2020年1月17日

    为Session类添加SNMPv3上下文

    2.1.3版----2020年1月18日

    增加IPv6测试选项

    2.2.0版-2020年1月21日

    添加SNMP代理

    2.3.0版-22/01/2020

    增加MIB解析器和模块存储

    2.4.0版-24/01/2020

    在代理中添加代理转发

    2.5版本。

    许可证

    Copyright (c) 2020 Mark Abrahams mark@abrahams.co.nz

    Copyright (c) 2018 NoSpaceships Ltd hello@nospaceships.com

    版权所有 (c) 2013 Stephen Vickers stephen.vickers.sv@gmail.com

    兹免费授予任何获得本软件和相关文档文件("软件")副本的人不受限制地使用本软件的权利,包括但不限于使用、复制、修改、合并、出版、分发、再许可和/或出售本软件副本的权利,并允许接受本软件的人在以下条件下这样做。

    上述版权声明和本许可声明应包含在本软件的所有副本或主要部分中。

    本软件 "按原样 "提供,没有任何形式的明示或暗示的保证,包括但不限于适销性、特定用途的适用性和不侵权的保证。在任何情况下,作者或版权持有者均不对因本软件或本软件的使用或其他交易而引起的、因本软件的使用或与本软件有关的合同、侵权或其他行为的任何索赔、损害或其他责任负责。

    查看原文

    赞 1 收藏 1 评论 0

    Reco 回答了问题 · 9月25日

    解决这种是svg的请求吗,为什么只有这两个,页面应该有很多svg呀?

    这是一个Data URI的请求,虽然数据就在HTML内。其他的svg如果是内嵌的,并不产生Network访问条目。

    为此,首先你需要了解到的是,加载一个svg有几个方法:

    1. 直接写。类似这样<html>...<svg></svg>...</html>
    2. 写入img,以data URI形式。类似这样<html>..<img data-original='data:image/svg+xml;utf8,<svg ... > ... </svg>'>...</html>,或者做base64.
    3. 外链接。

    你的情况是第二种,可以产生一个你看到的截图内的Network 访问条目。如果是第三种也会导致Network 访问条目。如果是第一种,它会在所在的html文件内一起加载,因此不会产生Network 访问条目。

    关注 3 回答 1

    Reco 发布了文章 · 9月24日

    Node.js中的 "for await ...of "语句的简单解释。

    嘿,你们好!

    几个月前,Deno--一种Node.js的后继者--发布了,在主页上有一个如何使用它的小演示。

    import { serve } from "https://deno.land/std@0.69.0/http/server.ts";
    const s = serve({ port: 8000 });
    console.log("http://localhost:8000/");
    for await (const req of s) {
      req.respond({ body: "Hello Worldn" });
    }

    突然,当看到第4行的await调用在for之后,但在(const req of s)之前时,我的眼睛就 "那是什么?"?

    我从来没有见过这样的东西,我的第一个想法是 "这是一个非常酷和奇怪的事情,deno做"....。

    想象一下我的惊讶,当我阅读了更多关于deno的资料后,我发现那一小段代码其实是有效的javascript,而且_它在Node.js中也是有效的,而我对它一无所知_。

    那么,这是什么呢,为什么我从来没有见过,我应该用在哪里呢,我是不是已经错过了?

    如果你也有同样的疑问,那么好!

    这篇文章将尝试回答这些问题!

    首先,先说一下。

    迭代器

    你见过这样的东西吗?

    class RandomNumberGenerator {
      [Symbol.iterator]() {
        return {
          next: () => {
            return { value: Math.random() };
          },
        };
      }
    } 

    如果你做了,那么对你来说很好,你可以跳到下一节!

    如果你还没有,那么让我们深入了解一下这个类的作用。

    这个类RandomNumberGenerator实现了[Symbol.iterator]@@iterator方法(当该方法通过Symbol属性定义时,我们用双@@来表示该方法)。

    [Symbol.iterator]@@iterator方法定义在一个对象上时,允许对象进行迭代!

    由于现在我们将@@iterator方法定义为RandomNumberGenerator类的一个实例方法,所以这个类的所有实例现在都是可迭代的。现在你可以通过运行下面的代码来测试它。

    class RandomNumberGenerator {
      [Symbol.iterator]() {
        return {
          next: () => {
            return { value: Math.random() };
          },
        };
      }
    }
    
    const rand = new RandomNumberGenerator();
    
    for (const random of rand) {
      console.log(random);
      if (random < 0.1) break;
    } 

    为了让一切都能正常工作,"@@iterator "方法必须返回一个包含 "next "方法的对象,并且 "next "方法需要返回另一个具有 "value "和 "done "属性的对象。

    value将包含返回的值,而done将是一个布尔值,如果它被设置为true将结束迭代。

    虽然 "value "是必须的,但 "done "可以省略,就像上面的例子一样(这允许我们定义_无限的迭代)。

    好吧,酷!我们现在可以让事情变得可迭代了。

    我们现在可以让事情变得可迭代了!

    这在什么时候才有用呢?

    我相信这高度依赖于你正在创建的业务逻辑的类型。

    例如Node.js设计模式这本书--我强烈推荐--给出了一个迭代矩阵元素的例子(你可能已经把它定义为一个数组)。

    另外我相信这篇文章强调了一些很酷的用法。它定义了一些非常酷的方法,看起来很有python风格。

    然而,如果你想知道我的诚实和个人意见,我还没有遇到过 "这是迭代器的一个伟大的用例 "的情况。

    无论如何,让我们回到我们文章的主题:我们还需要在for循环中添加 await什么?

    异步迭代器

    顾名思义,异步迭代器是我们在上面的例子中所做的异步版本。

    想象一下,我们不是返回一个随机数,而是返回承诺。那会是怎样的呢?

    如果我们把上面的例子改成这样,它就会是这样的。

    const simulateDelay = (val, delay) =>
      new Promise((resolve) => setTimeout(() => resolve(val), delay));
    
    class RandomNumberGenerator {
      [Symbol.asyncIterator]() {
        return {
          next: async () => {
            return simulateDelay({ value: Math.random() }, 200); //return the value after 200ms of delay
          },
        };
      }
    }
    
    const rand = new RandomNumberGenerator();
    
    (async () => {
      for await (const random of rand) {
        console.log(random);
        if (random < 0.1) break;
      }
    })(); 

    变化有哪些?

    1. 我们首先将Symbol属性改为 "asyncIterator",而不仅仅是 "iterator"。
    2. 我们把 "next "方法做成了一个异步函数。
    3. 我创建了simulateDelay函数,该函数返回一个承诺,在给定时间后解析给定值。
    4. 我们在for循环中加入了await
    5. 我们把循环放在Inmediately调用的函数表达式里面,是为了不出现顶层 await调用的问题。(注意:在Node.js 14+版本中不再需要这个功能)

    于是我们做了一个简单的程序,能够对一个对象进行迭代,以异步的方式获取其元素。

    如果你知道除了主页上的小deno例子之外,还有其他的异步迭代器的实现,请发邮件给我,或者在下面留言

    查看原文

    赞 6 收藏 3 评论 3

    Reco 发布了文章 · 9月23日

    技术趋势对决"■▄。React vs Angular vs Vue

    Image for post

    等等,我不是已经写过关于这个了吗?嗯......是的,但你们中太多人抱怨没有给你们一个实际的答案。所以这里有一些东西给那些喜欢具体数字和统计的人。

    我们将从招聘信息、开发者统计、下载量等因素出发,实际冲破React、Angular和Vue这些模糊的云雾,决定你在2019年应该为自己的职业生涯学习什么

    方法论。

    我们的目标是根据标准进行公正的搜索。

    工作需求 -这三个库和框架的实际工作需求和可用工作是什么。

    开发者使用情况--开发者和程序员在使用这3个库和框架中的哪一个?

    开发者意见--你要享受你正在使用的工具。开发者对这3个分别有什么感受?

    在本文中,你会发现上述主题的部分。理想情况下,我们希望选择需求量最大的工具,这样可以让我们有更高的就业几率,同时也要牢记我们要享受使用库的工作。最后,我们要选择一个不是处于衰退期,而是有着光明的未来,并且在开发者社区中不断成长的工具。让我们开始吧

    重要的。每隔一段时间,我都会就这个话题发表我的看法,但就像所有事情一样,你应该自己去研究,决定自己要学习和掌握什么。我以前写过这方面的文章](https://zerotomastery.io/blog...)。我为你提供了一些数字,你可以用这些数字,并根据自己的意愿进行扩展,自己决定。如果我遗漏了什么,或者你想补充什么,请在评论里告诉我。

    下面找到的所有数据和数字都是截至写作时(2018年12月10日)的最新数据。本文将每隔几个月更新一次,以跟踪趋势.

    职位需求

    我大胆猜测,这是你在决定职业生涯中学习什么最重要的因素之一,甚至是最重要的因素。毕竟,在有这么多东西要学的时候,时间是一种宝贵的资源,所以你要确保你选择的是能让你赚钱的。我们来看看数据吧。

    React、Angular和Vue开发者的招聘信息有多少?

    贴图

    上述数据,以不同方式显示:

    贴图

    _和实际数字:

    贴图

    我选择数据的标准如下。

    LinkedIn是现在科技界最大的招聘网站,所以这是一个全球范围内的开发者招聘信息搜索,每个库。

    IndeedSimplyHiredDice是美国3个大的科技招聘网站,所以搜索的地域是针对那里的。你可能不住在美国,但能看到在一个非常重要的科技地点发生的事情是好事。

    AngelList可能是创业公司最好的招聘网站。我们可以看到初创公司,以及较新的现代公司在科技领域的应用。

    Hired是一个帮助企业寻找工程人才的招聘平台。看到他们对公司正在寻找什么的看法很有意思,因为他们往往只发布大型科技公司的工作。

    远程是我为了好玩而特别添加的一个栏目。我使用了在indeed.com以及remote.co上找到的远程工作,以此来衡量3个框架中每个有多少远程工作机会。

    我们在数据中看到的:

    React和Angular是头牌。对于React来说,这并不奇怪,因为在过去的几年里,它一直是最流行的前端库(稍后会有更多的介绍),但看到Angular,尽管像stateofJS这样的地方称它为2019年的 "死亡",但还是令人惊讶。另一个令人惊讶的是,尽管最近在stateofJS的调查中,Vue.js得到了开发者的喜爱,但它的需求却如此之低。

    我的2分钱:

    学习React或Angular,如果你想拥有最多的就业选择。


    开发者使用

    我们来看看React、Angular和Vue的开发者使用情况是怎样的。

    下面是开发者从NPM下载的总次数:

    贴图

    https://www.npmtrends.com

    以及Github的统计数据:

    贴图

    Github星相互比较可视化:

    贴图

    但是如果我们看一下顶级的开源项目,我们会在里面看到Angular和React(有点),但是没有Vue:

    贴图

    https://octoverse.github.com/projects

    而以下是基于搜索词的google趋势(这在技术上也可能与工作需求有关):

    贴图

    而另一个google的趋势与术语略有不同:

    贴图

    并添加 "Angular "作为术语,因为AngularJS是Angular的老版本(但请记住,这包括所有与Angular有关的技术以外的术语):

    贴图

    我们在数据中看到的:

    总的来说,当涉及到下载和Github活动时,我们看到所有这些库都在积极增长。开发者正在使用这些库,这不是问题,但我们肯定看到Github明星和Vue.js的实际使用量不成比例。总的来说,Vue.js再次还不能与Angular和React竞争。不过看起来Angular和React的增长速度开始有所放缓。

    我的2分

    相对于开发者的实际使用情况,React的开发者活跃度似乎不错。Angular似乎排在第二位,而Vue再一次似乎并没有像人们想象的那样被使用(虽然在中国似乎有更高的使用趋势)。虽然React和Angular使用速度放缓可能意味着是由于一些用户转而使用Vue。如果你是一个开发者,那么围绕React的生态系统的文档、博客文章和活动可能会比Angular或Vue多。如果这对你很重要的话,请坚持使用React。

    速记: Vue进入明年有一个有趣的趋势。Vue经常被中国巨头使用,比如阿里巴巴、百度、腾讯,甚至是小米和大疆,而不是Facebook和谷歌创造的React或Angular。我预计中国市场会保持特别快的增长,因为Vue是一个开源的独立库,不依附于西方大科技巨头(_没有数据支持这一点)。


    开发商意见

    你必须热爱你正在使用的工具。虽然钱很重要,但它不是一切。理想的情况是,你希望从事的是你喜欢的工作。让我们来看看这里的数字。

    FromStackOverflow survey(可能是那里最好的关于开发者趋势的调查) Wanted, Loved, and Dreaded by Developers:

    Image for post

    Image for post

    Image for post

    Image for post

    Image for post

    Image for post

    FromstateofJS survey, whichunfortunately is geared more towards early adopters and React and Vue communitiesso the data is a little bit biased:

    Image for post

    Image for post

    https://2018.stateofjs.com/front-end-frameworks/overview/

    React:

    Image for post

    Image for post

    https://2018.stateofjs.com/front-end-frameworks/overview/

    Angular:

    Image for post

    Image for post

    https://2018.stateofjs.com/front-end-frameworks/overview/

    Vue:

    Image for post

    Image for post

    https://2018.stateofjs.com/front-end-frameworks/overview/

    我们在数据中看到的:

    没有什么新的东西是我们以前没见过的。React似乎受到其开发者的喜爱,人们似乎总体上对这个库很满意。React似乎在上面的每一个数据点上都击败了Angular,但幅度不大。虽然JS现状调查显示很多人对Angular不满意,StackOverflow也提到人们对它的喜爱程度不如React,但正如我们在上一部分所看到的,工作需求对它的需求还是存在的。Vue再一次成为其中的一个小角色,虽然我们看到Vue社区对Vue的热情很高,但它仍然是一个小角色......但很好,可以关注一下。

    我的2分钱:

    如果你想在开发时获得快乐,React或Vue是你最好的选择。


    但这是最重要的部分

    以上图表都显示了本文撰写时的时间快照。理想的情况是,我们希望有一个数字的趋势(尤其是招聘信息),以确保我们没有乘着一个垂死的浪潮,或者是一个炒作的浪潮。出于这个原因,我打算每隔几个月用更多的图表来更新这篇文章,这样我们就有历史数据来注意和未来的趋势。如果你想保持更新,请在Twitter上关注我,并订阅我的博客


    结语??""。

    在这里得出你自己的结论很重要......好吧,但就因为你问得好,我的意见是,根据上面的信息,React是2019年的方向。你要选择一些人们在公司使用的东西,但也要选择一些正在成长的东西,而不是作为一种趋势而减少。你希望有一个强大的社区,有良好的文档和资源,你希望使用一个你会喜欢工作的工具。React满足了所有这些要点。这并不意味着你应该忘记Angular或Vue。它们都是很好的库和框架,没有什么东西是永恒的。我唯一的建议是,如果你的时间有限,你喊着 "TELL ME! 就告诉我一个要研究和学习的",你喊得很大声,我就不得不说React。

    接下来你想看什么
    我想围绕编程的其他主题做这种技术趋势分析,每隔一段时间就会发布一次。

    **GraphQL与REST?
    PHP vs Node.js?(其实在这里写过)
    Javascript vs Python?

    如果你想看更多,请在评论中告诉我。

    记住,每个人都有自己的观点,每个工具的存在都是为了解决问题。在技术领域没有赢家通吃([至少在BostonDynamics的AI机器人接管我们的世界之前没有)(https://www.youtube.com/watch...,所以聪明一点,只需使用这里的数据和你自己的数据,根据你的需求做出明智的决定。
    查看原文

    赞 1 收藏 1 评论 4

    Reco 发布了文章 · 9月22日

    2020年顶级无服务器计算平台

    回顾2020年所有主要的无服务器计算服务。

    贴图

    随着云平台的成功,我们已经看到了允许您像使用自己的远程基础设施一样使用远程基础设施的产品(基础设施即服务)。我们看到的产品允许你使用不同的产品来使用远程基础设施,而不必担心它(平台即服务)。现在,我们也有能力深入到代码层面(顺便说一句,这真是太神奇了),只要我们触发函数,这些函数就会被执行,谁也不知道在哪里(Functions as a Service)。

    在这篇文章中,我将快速介绍一下有哪些顶级的平台可以让你远程执行自定义计算,或者换句话说。无服务器计算平台。

    快速介绍。什么是无服务器计算?

    无服务器计算是指能够执行自定义计算,而不必担心计算发生在哪里。当然,服务器是存在的(你认为代码会在哪里运行呢?),但事实上,你不必考虑它们,_意味着你可以认为它们是不存在的。

    当然,我所说的计算,是指你可以写的实际函数。想想看:在软件开发中,有些时候,你需要创建整个服务,只是为了提供几个功能,同时,这些功能偶尔也会被使用,但你仍然必须让这个服务在99.9%的时间里都能正常运行,否则用户可能会需要它们,如果它们失败了,那么你就麻烦了。

    如果,你不依靠这99.9%的正常运行时间,而是能保证每当你需要执行该功能时,就启动一个服务器,里面有你需要的所有东西(包括你自己的代码),在执行结束后,所述服务器就会停止?对于一个可能有30%的时间会被执行,但99.9%的时间需要启动以备不时之需的服务,这样你能节省多少钱?这就是无服务器计算平台最大的好处之一,你不用担心基础设施,你只需要担心执行你需要的计算的代码。

    有趣的是,这并不是一个新的概念,事实上,外面第一个无服务器服务是在2006年由Zimki推出的,不过它一直没有成功,最终关闭。从2006年到现在,第一个真正的赢家是亚马逊,他通过Lambda,在2014年成功推出并普及了这种服务模式。如今只有它一家吗? 当然不是! 和其他所有基于云的服务一样,各大平台都有,在本文中,我将快速介绍它们。


    为什么要采用无服务器化?

    了解什么是无服务器计算很重要,但除此之外,你为什么要使用它?你准备好失去对服务器及其配置的控制了吗?

    事实上,这种模式有不少有趣的好处,我个人认为它们绝对超过了你对它的所有潜在保留意见。

    • 无服务器架构是可扩展的 无服务器架构是可扩展的:它们就是这样构建的,你不必担心这个事实。你还想从一个架构中得到什么?
    • 你只需要担心编写你的代码,其余的模板代码是不需要的。这绝对是一个节省时间的方法,尤其是现在使用的一些技术(也就是node_modules,有人知道吗?
    • 不需要处理服务器配置,因为,嗯......没有服务器(至少,从你的角度来看没有)。这也是一个巨大的时间节省,不需要安装和配置任何东西,只是假设一切都准备好了,你可以使用(因为它是)。
    • 你只需为你使用的东西付费,所以你不需要为一个总是开着的服务器付费,即使它没有被使用,有了这种模式,你只需为你的代码每次被触发时执行的几秒钟付费。

    本质上,从理论上讲,一切似乎都表明,无服务器模式会简化你开发过程中的方方面面以及相关的管理任务。

    说到这里,我听到你在问:有什么问题?当然,这种模式也有一个缺点。

    为什么你要避免Serverless?

    在科技行业中,没有什么灵丹妙药;没有一种产品或工作模式能满足你的所有需求,正因为如此,采用无服务器方式并不总是正确的举措。

    例如,无服务器方式有一些缺点。

    • 调试和测试你的代码不是那么简单。你很可能需要一些专门的工具来重现你的代码在无服务器环境下的表现。毕竟,没有服务器就意味着你无法访问正常的环境,在这种环境中你可以调试代码的执行。
    • 性能可能是个问题。现在不要误解我的意思,我并不是说无服务器功能很慢,一点也不慢。有一些方法可以让你的代码执行热身,这样如果它经常被使用,就不会受到惩罚。但这毕竟是一个无服务器的环境,正因为如此,平时不常用的函数(或不经常调用的函数)会花费更长的时间来执行。这取决于提供商和他们采用的执行方案。
    • 无服务器计算并不是为了持续运行而设计的,这意味着你必须重新思考构建逻辑的方式。这是一个改变你的观点的问题,但这可能是一个挑战,特别是在一开始。
    • 厂商锁定绝对是真实存在的。我的意思是,这对你来说可能不是一个问题,但如果你想保持你的代码通用性和厂商无关性,这可能不是办法。你的整个执行过程将是特定于厂商的,所以在决定为模型之前要考虑这一点。

    也就是说,这些与其说是缺点,不如说只是可以被认为是警告。如果你把它们考虑进去,你就会没事的。

    现在我们把这些都说清楚了,让我们快速了解一下主要的无服务器计算提供商。

    主要供应商

    在考虑提供无服务器服务的云提供商时,有很多选择。所以为了避免写一本小书来介绍所有的选择,我将列出主要的选择,如果你还在考虑其中没有一个适合你,至少你会有一个更好的想法来寻找什么。

    AWS Lambda

    绝对不是这种模式的发明者,但它仍然被认为是第一个真正把所有事情都做对的人,并且让它流行起来。也许是它背后的商业模式,也许只是时间问题,等待技术以可承受的价格提供,但自2014年以来,亚马逊一直在提供Lambda服务,这是在无服务器计算方面最大的名字之一。

    他们提供了广泛的编程语言,你可以使用,而且他们与所有的服务都有集成,允许你根据他们的产品触发的许多事件来触发你的功能的执行。

    在他们的网站上,你可以看到如何利用他们的一套服务与lambda函数一起工作,并实现以下功能。

    • ML模型的数据预处理
    • 实时数据分析用于检测趋势

    *对外服务通信

    这绝对是一个非常通用的服务,并且拥有您可能需要的所有文档和示例。

    Azure功能

    AWS第一个真正的竞争者可能是微软,但2年后,在2016年。他们为FaaS提供的服务与亚马逊的非常相似,当然,集成了他们自己的一套服务和相关的触发器。

    如果你要选择AWS的Lambda的其他服务,这绝对是一个完美的选择。

    请记住,这些无服务器计算服务是理想的,当与同一个云提供商的产品结合使用时,效果最好,如果你想让事情跨云工作,那么你可能会开始遇到一些复杂的问题(当然,这取决于你到底想实现什么)。

    谷歌云功能

    令人难以置信的是,谷歌姗姗来迟。他们可能拥有世界上最大、最刻薄的搜索引擎,但他们为FaaS提供的产品是在2017年,最初,他们的产品比其他替代品慢了不少,这在当时甚至是最差的选择。

    也就是说,一年后,2018年左右,他们设法改善了这方面的情况,他们终于从他们的产品中删除了 "测试版 "标签,并确认现在已经可以用于生产使用。

    如今,他们的服务与竞争对手不相上下,所以虽然他们有一个艰难的开始,但其绝对是一个值得考虑的。

    我们该如何选择?

    除了根据你当前的提供商进行选择(即如果你已经与AWS合作,你可能会想选择他们,Azure和GCP也是如此),你需要一些细节来了解哪一个是适合你的选择。

    所以现在让我们来看看一些硬性的、无偏见的数据,应该可以帮助你做出这个选择。

    免费层

    了解他们对你的流量收多少钱和了解他们收多少钱一样重要。

    • AWS Lambda:100万次执行/月是免费的
    • Azure功能。每月免费执行100万次
    • 谷歌云功能。每月免费执行200万次

    赢家:很显然,这部分的赢家是谷歌,每个月有2M的免费执行量。也就是说,如果你只是在试水,他们中的任何一家都能提供每月足够的免费执行量供你玩耍。

    支持的编程语言

    这些毕竟是函数,你要用什么语言来写,肯定是个大问题。而且由于基础设施是管理式的,所以你无法真正挑选出你最喜欢的那一个,相反,你必须从可用的选项列表中选择。

    • AWS Lambda。开箱即用,支持Java,Go,PowerShell,Node.js,C#,Python和Ruby。并可选择创建自定义运行时,以使用任何你想要的编程语言
    • Azure 函数。C#、F#、JavaScript、JAVA、PowerShell、Python和TypeScript。具有提供以任何语言编写的HTTP服务作为实际函数的实验性功能。
    • 谷歌云函数。JavaScript、Python、Go、JAVA

    赢家:这里的明显赢家是AWS,Azure紧随其后。为什么呢?因为他们的产品比谷歌的大得多,而且他们都提供了对自定义语言的支持(不像谷歌),但微软的替代方案仍处于预览模式,这意味着它并不真正稳定。

    最大执行时间

    虽然在编写无服务器功能时,应该考虑到这些功能的运行时间很短,但在一些用例中,可能需要更长的时间。

    • AWS Lambda。最高15分钟 *Azure Functions:
    • Azure Functions。通常最多10分钟,但如果你有高级计划,保证最多60分钟。 * 谷歌云函数:
    • Google Cloud Functions:默认为1分钟,但可以保证最长60分钟。默认为1分钟,但可以延长到9分钟。

    赢家:如果你要用基本计划的话,这个有点难,AWS Lambda显然是这里的赢家,15分钟。你能想象一个函数运行那么久吗?当然,可能还有一些用例,但这绝对是一个很长的时间。另一方面,如果你愿意多付一点钱,Azure的高级计划提供了无限的运行时间(尽管他们只保证最多60分钟,这听起来又很疯狂,应该是足够了)。

    实际计算成本

    最后,为了一个相当大的执行计划,我们可以期望每月支付多少钱?

    贴图

    贴图

    摘自http://serverlesscalc.com/

    通过使用serverlesscalc.com,我们可以快速比较在1.5Gb内存的服务器上执行100万个函数,每个函数平均运行1秒,需要花费多少钱。

    你可以使用这个网站来玩弄这些数字,看看你所看到的差异是什么样的,或者你甚至可以使用每个供应商自己的计算器来对这些估计有更多的控制。

    最后,考虑一下即使我使用100万次执行作为例子,他们都有一个免费的层级_至少_这个数量,你仍然不得不支付。这是因为即使你没有为请求付费,你仍然要为计算时间付费。如果你使用较低级别的服务器(即在这种情况下内存较少),你的成本会减少。

    赢家:最后,这里的赢家似乎又是AWS Lambda,紧随其后的是Azure,最后谷歌云功能是最昂贵的选择。

    结束语

    那么你会选择哪一个呢?还有很多其他因素需要考虑,说实话,这些因素都取决于你的背景,你要解决的一系列需求,以及你是否已经有了一个云提供商。你可以用这个列表作为指导,但记得在做出选择之前要考虑到自己的实际情况。

    最后,哪一个是你最喜欢的?或者甚至更好的是,你会不向其他人推荐哪一个?请在下面留下你的评论,让别人能从你自己的经验中受益!

    _下期再见!_。

    查看原文

    赞 1 收藏 1 评论 0

    Reco 发布了文章 · 9月21日

    Node.js无处不在的环境变量!

    您构建的 Node.js 应用程序首先要在计算机上运行。您知道,应用程序在任何需要运行的地方都能工作是很重要的。这可能是在你同事的电脑、公司内部服务器、云服务器或容器内部(可能使用Docker)。输入环境变量.

    贴图

    使用环境变量的说明性指南

    环境变量是使用 Node.js 开发的一个基本部分,它允许您的应用程序根据您希望它们运行的环境而表现出不同的行为。无论您的应用程序在哪里需要配置,您都可以使用环境变量。它们非常简单,而且非常漂亮!这篇文章将向您介绍如何创建和使用环境变量。

    这篇文章将引导您创建和使用环境变量,从而创建一个可以在任何地方运行的 Node.js 应用程序。


    人们经常问我,我是如何决定什么是学习和投入时间的,什么是放任自流的。对我来说,这是从这两个相同的问题开始的(例如,问为什么和何时)。我敢打赌,你也会做类似的事情,不是吗?

    为什么和什么时候?

    当有人告诉你某件事情很重要,你应该使用它时,我建议问两个简单的问题。

    1. 为什么我应该使用它?
    2. 我应该在什么时候使用它(或不使用它)?

    **为什么?

    现在是你问我为什么要使用环境变量的时候了。去吧;没关系。

    如果您关心使您的应用程序在任何计算机或云上运行(也就是您的环境),那么您应该使用它们。为什么?因为它们将您的应用程序的所有环境特定方面外部化,并保持您的应用程序封装。现在,您可以通过修改环境变量在任何地方运行您的应用程序,而无需更改您的代码,也无需重新构建它! **什么时候?

    **什么时候?

    好了,现在你问我什么时候应该使用它们。简而言之,你的代码中任何会根据环境而改变的地方。当你看到这些情况时,对任何你需要改变或配置的地方都要使用环境变量。

    下面是一些你应该考虑使用环境变量的常见场景的具体例子。

    • 侦听哪个HTTP端口
    • 您的文件在什么路径和文件夹中,您想提供的服务

    *指向开发、暂存、测试或生产数据库。

    其他的例子可能是服务器资源的URL,测试与生产的CDN,甚至是在UI中通过你的应用所处的环境来标记你的应用。

    让我们来探讨一下如何在Node.js代码中使用环境变量。

    使用环境变量

    您可能正在为 Express 服务器设置端口号。通常情况下,不同环境(例如;暂存、测试、生产)中的端口可能需要根据策略进行更改,并避免与其他应用程序发生冲突。作为开发人员,你不应该关心这些,实际上,你也不需要关心。下面是如何在代码中使用环境变量来抓取端口。

        // server.js
        const port = process.env.PORT
        console.log(`Your port is ${port}`);
    

    试试这个。创建一个名为 "env-playground "的空文件夹。然后创建一个名为server.js的文件,并将上面的代码添加到其中。现在,当你执行node server.js时,你应该看到一条消息说 "Your port is undefined"。

    你的环境变量不在那里,因为我们需要把它们传递进来。让我们考虑一些可以解决这个问题的方法。

    1.使用命令行

    1. 使用.env文件。

    命令行

    将 port 传入您的代码的最简单方法是在命令行中使用它。在命令行中指定变量的名称, 后面是等号, 然后是值。然后调用您的 Node.js 应用程序。

    PORT=8626 node server.js

    你会看到端口显示在这样的消息中:"你的端口是8626"。

    _这个8626是什么东西?为什么不是4200或者3000或者更传统的端口?好吧,我是迪斯尼的忠实粉丝,有一个叫史迪奇的角色也被称为实验626._。

    你可以重复这个模式,也可以添加其他变量。下面是一个传递两个环境变量的例子。

        PORT=8626 NODE_ENV=development node server.js。
    

    按照环境变量名后加等号再加值的模式。这很容易做到,但也太容易犯输入错误了。这就有了下一个选项。

    减少.env文件的混乱

    一旦你定义了其中的几个,你脑海中的下一个念头可能就是如何管理它们,使它们不会成为维护的噩梦。想象一下,数据库连接、端口和密钥都有好几个。当你把它们都打在一条线上时,这并不能很好地扩展。而且可能会有私人信息,如密钥、用户名、密码和其他秘密。

    从命令行运行它们当然很方便。但它也有它的缺点。

    1. 没有一个很好的地方可以看到变量列表。

    2.从命令行打字太容易出错了。
    3.要记住所有的变量和它们的值并不理想。
    4.即使使用npm脚本,你仍然必须保持它们的最新性。

    对于如何组织和维护环境变量,一个流行的解决方案是使用.env文件。我真的很喜欢这种技术,因为它使我可以在一个地方快速读取和修改它们变得超级容易。

    在你的应用程序的根目录下创建.env文件,并将你的变量和值添加到其中。

        NODE_ENV=development
        PORT=8626
        _# 在这里设置你的数据库/API连接信息。
        _API_KEY=**************************
        API_URL=**************************
    

    记住你的.gitignore文件

    .env文件是一个很好的方法,可以在一个地方看到所有的环境变量。只要确保不要把它们放到源代码控制中。否则,你的历史记录将包含对你的秘密的引用。

    创建一个.gitignore文件(或者编辑现有的文件,如果你已经有了),并添加.env到其中,如下图所示。.gitignore文件会告诉源控制忽略你列出的文件(或文件模式)。

    贴图

    贴图

    小心将.env添加到.gitignore文件中,并在添加你的_ .env_之前提交该更改。否则,你就有可能将你的.env_的早期版本提交到源代码控制中。

    您可以通过使用 Visual Studio Code (VS Code)中的命令调色板将一个文件添加到您的.gitignore文件中。按照以下步骤进行。

    1. 在VS Code中打开你要添加到.gitignore的文件。
      在Mac上用CMD+SHIFT+P打开命令调色板,在Windows上用CTRL+SHIFT+P打开。
    2. 输入 "忽略"。
    3. 选择 "Git: 从菜单中添加文件到.gitignore"

    这将把你当前打开的文件名添加到.gitignore文件中。如果你没有创建.gitignore文件,它将为你创建!

    贴图

    将.env加入到.gitignore中。

    您的.env文件的语法高亮

    如果你使用VS Code,你会想要添加dotenv扩展。这将为你的.env文件的内容提供语法高亮,并使你更容易使用.env文件中的变量。

    下面是安装了dotenv扩展的VS Code中的文件。

    贴图

    .env文件在VS Code中具有语法高亮功能。

    读取.env文件

    现在你可能在想,有些东西必须寻找.env文件,你是对的!你可以自己写代码来寻找文件,解析,并将它们读到你的Node.js应用中。

    你可以写你自己的代码来找到这个文件,解析它,然后把它们读到你的Node.js应用中。或者你也可以通过npm找到一种方便的方法,将变量一次性读到你的Node.js应用中。你很可能会遇到npm上的dotenv包,它是我的最爱,也是我推荐你使用的。你可以像这样npm install dotenv来安装它。

    然后你可以在你的项目中要求使用这个包,并使用它的config函数(config也有一个load的别名,以防你在野外看到)来寻找.env文件,读取你定义的变量,并将它们提供给你的应用程序。

    按照以下步骤进行。

    1. 创建一个package.json文件

    2.安装dotenv的npm包。
    3.写代码读取.env
    4.运行代码

    创建一个package.json文件

    你需要一个package.json文件来配置你的版本,跟踪你的依赖关系,并包含你的npm脚本。通过运行以下命令来实现

    npm init -y

    这将创建一个 "package.json "文件,其中包含基本设置。

    从npm安装dotenv

    你需要读取.env文件,而npm上的dotenv包可以很好地完成这个任务。运行以下命令安装dotenv包

    npm install dotenv

    这将把dotenv包和它的文件添加到你的node_modules文件夹中,并在package.json文件中为dotenv创建一个条目。

    读取.env文件

    是时候用一点代码读取.env文件了。用下面的代码替换你的server.js文件的内容。

    // 服务器.js
    console.log(Your port is ${process.env.PORT}); _// undefined
    _const dotenv = require('dotenv');
    dotenv.config();
    console.log(Your port is ${process.env.PORT}); _// 8626_。

    该代码显示了PORT环境变量的初始值,该变量将是未定义的。然后,它需要dotenv软件包,并执行其config函数,读取.env文件并设置环境变量。最后一行代码显示 "PORT "为8626。

    运行代码

    现在,使用以下命令,在不传递端口的情况下,在命令行中运行代码

    node server.js

    请注意控制台日志信息显示端口最初是未定义的,后来变成了8626。dotenv软件包读取并设置这些值,实际上是为您做了肮脏的工作。

    轻松找到您的变量

    现在,我们有了一个在.env文件中创建变量的单一地方,我们可能会考虑如何在Node.js代码中轻松检索所有这些变量。为什么要这样做?这个问题问得好! 好吧,你可以在代码中使用以下语法来引用变量。

    process.env.YOUR_ENV_VAR_GOES_HERE

    但是,你是否想在你需要的地方都这样做(你可能在多个地方需要它们)?还是应该把我们所有的环境变量收集在一个地方? 当然是后者! 为什么这么说呢? 如果你真的在所有需要的地方都引用变量,可能会比在一个地方引用变量更难重构和维护。

    我建议创建一个模块,负责收集环境变量。这样可以很容易地一次性看到它们,并将它们映射到可读的名称上。

    这里有两个不错的选择可以考虑。

    1. 在一个模块中手动设置和导出它们。

    2.使用一个库来读取并在一个模块中导出它们。

    这两种技术都涉及创建一个模块,但它们在环境变量的映射和暴露方式上有所不同。让我们仔细看看这两种技术,并权衡一下它们的区别。

    手动组织

    创建一个模块(下面的例子显示的是config.js),在这个模块中,你可以收集变量,将它们映射为名称明确且可读的属性,并将它们导出。

    让我们在一个名为config.js的文件中创建一个新模块。然后将以下代码复制并粘贴到文件中。

        // config.js
        _const_ dotenv = require('dotenv');
        dotenv.config();
        module.exports = {
          endpoint: process.env.API_URL,
          masterKey: process.env.API_KEY,
          port: process.env.PORT
        };
    

    这个例子展示了我们如何将环境变量整合到一个文件中。

    让我们再次修改server.js,这次是导入config.js模块并使用它来访问我们的环境变量。用下面的代码替换server.js的内容。

        // server.js
        _const_ { port } = require('./config');
        console.log(`Your port is ${port}`); _// 8626_
    

    使用node server.js运行代码,你会看到 "你的端口是8626 "的消息。环境变量正在被config.js文件中的dotenv代码读取。你的server.js文件导入config.js中的模块并提取端口变量。

    你可以把config.js文件导入到你需要的地方,然后用destructuring来提取你需要的东西。你只调出了port,但你当然可以调出模块中导出的任何一个值。

        const { endpoint, masterKey, port } = require(‘./config’);
    

    这个技术有什么价值?

    1.它很容易
    2.明确所有环境变量的映射方式。
    3.你可以将变量重命名为更易读的属性。
    4.你可以从非环境变量中添加其他配置属性。

    这里的关键点是,config模块的目的变成了收集和暴露所有的配置,不管它来自哪里。

    使用库进行整理

    手动技术的一个后果是,当你添加一个新的环境变量时,你必须把它添加到config模块中。例如,如果你需要制作一个新的变量,你需要进入这个配置模块,并添加这样的内容。

        visionApiUrl=process.env.VISION_API_URL
    

    我不介意这个后果,因为无论如何我都要通过我的代码来使用新的值。但我指出这一点是因为有一种方法可以自动收集它们。

    dotenv npm包中的dotenv.config()函数会读取.env文件,将变量分配给process.env,然后返回一个包含内容的对象(命名为parsed),如果失败的话,它还会抛出一个错误。

    下面的代码读取.env文件,并收集envs对象中的变量。

        // config.js 
        _const_ dotenv = require('dotenv');
        _const_ result = dotenv.config();
        _if_ (result.error) {
          _throw_ result.error;
        }
        _const_ { parsed: envs } = result;
        console.log(envs);
        module.exports = envs;
    

    然后,你可以从一个模块中导出这个对象,并再次在你的应用程序中轻松访问它。你访问变量的代码可能是这样的

        const { endpoint, masterKey, port } = require(‘./config’);
    

    你应该用哪个方案?这由你自己决定。但请考虑是否要将npm包dotenv作为运行时依赖。也许在更高的环境(比如生产环境)中,还有其他的方法来获取环境变量,在这些环境中,安全是最重要的。你甚至希望你的node应用中的代码能够读取如此重要的文件吗?你可能会觉得很酷,但如果有一种方法可以读取变量,并在你的package.json文件中把dotenv包变成一个devDependency呢?

    我建议你使用第一种技术,即在模块中手动设置环境变量,比如config.js。这允许你删除dotenv作为运行时的依赖--假设你还做了一件事:预加载环境变量。这也允许你减少你的运行时依赖性。

    减少您的依赖性

    你有很多对npm包的依赖。当你计划你的应用程序的生命周期和维护时,每一个都是你应该考虑的。你可能已经在考虑如何将你的依赖性减少到最低限度。我也是这样做的。

    dotenv包是很棒的,但我们不需要它成为运行时的依赖。dotenv包提供了一个选项,你可以在你的代码之外预加载变量。你可以加载变量,并从我们的代码中删除读取.env文件的代码。更少的代码就是更少的行数,可能会中断或被维护。

    还不放心吗?您是否考虑过如何在云中访问这些环境变量?你希望你的代码试图读取一个文件吗?我对此投了一个响亮的 "不"。我更希望我的代码不要试图读取文件,因为如果我把它带到云端,我希望这些解决方案甚至没有.env文件。

    如何从我们的代码中删除dotenv的运行时依赖,但又不失去已经获得的价值?首先,在安装dotenv npm包的时候,你可以像这样把它保存为开发依赖关系。

        npm install dotenv --save-dev。
    

    然后删除所有在dotenv上使用require的代码,包括本文之前提到的dotenv.config()代码。这包括本文前面提到的dotenv.config()代码。

    现在的问题是,你之前是依靠dotenv来加载环境变量的。这就是预加载选项发挥作用的地方。

    你可以使用- require ( -r ) 命令行选项来预加载dotenv来运行你的node应用。下面的命令将使用dotenv从文件.env中预载所有环境变量,并使它们在你的应用程序中可用。所以你现在已经从你的代码中删除了所有对dotenv的引用。

        node -r dotenv/config server.js
    

    当你想让你的应用运行在文件不存在(也许不应该存在)的地方时,这很有用,比如在运行中的docker容器或云服务器中。可以从node -h内看到-r选项的说明:

        -r, --require=...                   module to preload (option can be repeated)
    

    Leveraging npm Scripts

    我强烈建议把你的命令放到一个npm脚本中。这使得它更容易运行,而不是输入所有这些标志。也许你可以为它创建一个自定义脚本或者使用npm启动脚本。下面是你使用自定义脚本时脚本的样子。

        scripts: {
          "start_local": "node -r dotenv/config server.js"
        }
    

    命令npm run start_local就会启动这个命令。当然,你可以随意命名这个脚本。

    为什么不使用npm start?问得好,你当然可以这样做。你当然可以这样做。我喜欢把npm start保留给我在没有docker容器的情况下如何在生产中运行它,这可能只是像这样。

        scripts: {
          "start": "node server.js"
        }
    

    这里的关键是,无论使用哪种npm脚本,你都在运行完全相同的node代码!区别在于你的变量如何设置。区别在于如何设置你的变量。你现在已经从你的应用程序中抽象出了你的配置。

    好的npm工具

    如何记住所有的npm脚本?这很简单 - 你不需要!我依靠伟大的工具来帮助运行我的npm脚本。虽然我喜欢假装我知道所有的脚本名称和它们的具体作用,但事实上,我更希望有一个工具可以显示出列表,我可以选择它。

    在我选择的代码编辑器中,有两个很棒的工具可以做到这一点。VS Code

    1.npm的脚本大纲

    1. npm扩展

    npm脚本大纲是内置在VS Code中的,并显示在资源管理器视图中。请注意下面的截图显示了我的package.json文件中的npm脚本,如果你在你的项目中没有看到这个,请确保将npm.enableScriptExplorer设置为true。如果你在你的项目中没有看到这个,请确保在VS Code的settings.json中把npm.enableScriptExplorer设置为true。

    你可以右键点击一个npm脚本,然后打开、运行或调试它。

    贴图

    如果你像我一样喜欢用手敲键盘,那么你可能更喜欢VS Code的npm扩展。在安装了VS Code的npm扩展之后,你就可以在命令板上运行npm脚本了,在Mac上只要输入CMD+SHIFT+P就可以了。

    只要在Mac上输入CMD+SHIFT+P,或者在Windows上输入CTRL+SHIFT+P。然后开始输入npm,选择 "npm: 运行脚本",如下图所示。

    贴图

    然后你会看到一个npm脚本的列表。从这里开始,你可以开始输入你想要的脚本的名字,直到你想要的脚本被高亮显示。或者你使用箭头选择你想要的脚本。然后按 "ENTER "来运行它。

    我喜欢这样,因为它让我的手放在键盘上,这感觉比在鼠标/触控板和键盘之间切换更有效率。

    给一个这样的尝试。

    无论在哪里运行,你的代码都是一样的。

    你的应用程序没有意识到环境变量的来源。您运行它的方式,即预加载,是在您的本地机器上提供它们。

    让我们进一步探讨为什么你可能希望你的应用程序对其配置没有意识。

    Docker容器

    想象一下,我们正在一个Docker容器中运行。容器的传统指导说,容器内的应用不应该知道它们的配置。这样一来,只要容器和运行容器的任何东西就必须提供哪些变量达成协议,容器就可以在任何地方运行。

    在云端运行的

    当你把我们的应用带到云端时,各种云提供商也都有办法让你设置环境变量。因此,再次强调,如果应用程序仅仅使用变量,而运行它的东西(在这种情况下,云提供商的服务)为您提供了定义这些变量的方法,您就可以了。

    • 你的应用程序与其配置的这种分离,很大程度上来自于被称为12因素的指导。如果你还没有阅读过这方面的内容,请在这里了解更多_ https://12factor.net__。

    与您的团队分享

    你的队友如何知道要为你的应用创建哪些环境变量?他们应该在你的代码中搜索这些变量吗?他们应该打电话问你吗?当然不是,你没有时间亲自去拜访每一个开发者!

    当你的.env文件没有被推送到源码控制中时(这是不应该的),重要的是要让大家清楚这个文件的形状应该是什么样的。我推荐的技术是创建一个名为.env.example的文件,其中包含变量,但使用假值。这个文件可能看起来像下面的模板。

    .env.example

    NODE_ENV=开发
    PORT=8626
    # 在这里设置你的数据库连接信息 API_KEY=your-core-api-key-goes-here

    你的团队可以把这个文件的内容复制到他们自己的.env文件中,然后输入他们需要的值。在示例文件中列出非机密的值是完全正常的。注意到.env.example文件中的PORTNODE_ENV都被设置了,但API_KEY没有被设置。明智地选择你推送到源代码控制的内容。

    然后,你可以在你的README.md中引用这个文件,你的队友可以很快学会如何设置自己的值。问题解决了。

    环境变量的作用

    这只是对如何使用环境变量以及一些可以使用环境变量的神奇工具的一瞥。总的来说,我建议你使用环境变量,并遵循以下步骤。

    1. 创建一个.env文件

    2.在你的.gitignore文件中忽略它。
    3.使用VS Code来编辑你的.env文件。
    4.安装VS Code的dotenv扩展
    5.安装VS Code的npm扩展
    6.读取dotenv npm包中的.env文件作为开发依赖。
    7.使用dotenv的预加载选项来删除对它的任何运行时引用。
    8.使用npm脚本来运行你的node应用。
    9.为你的变量创建一个名为.env.example的模板文件。

    如果你喜欢我使用的字体和主题,它们是Dank Mono和Winter is Coming (Blue)。你可以找到这里的Dank Mono字体并安装这里的Winter is Coming主题_。我最后一次检查时,字体的价格是40英镑,而主题是免费的_。

    但是等一下,你想让你的服务器有一个免费的字体吗?你希望你的服务器有一个.env文件吗?你使用Docker或云服务器吗?这些都是要问自己的好问题。你在这里学到的方法是一个基础,可以与其他解决方案协同工作。我很快会在未来的文章中深入探讨这个问题,敬请期待。

    查看原文

    赞 7 收藏 6 评论 0

    认证与成就

    • SegmentFault 讲师
    • 获得 937 次点赞
    • 获得 12 枚徽章 获得 0 枚金徽章, 获得 3 枚银徽章, 获得 9 枚铜徽章

    擅长技能
    编辑

    (??? )
    暂时没有

    开源项目 & 著作
    编辑

    注册于 2014-04-10
    个人主页被 5.1k 人浏览

    bt365体育投注