javascript 代理_查看所有13个JavaScript代理陷阱

news/2024/7/7 18:59:46

javascript 代理

Proxies are a really cool JavaScript feature. If you like meta programming you probably are already familiar with them. In this article we are not going to get in to programming design patterns or get meta or even understand how proxies work.

代理是一个非常酷JavaScript功能。 如果您喜欢元编程,那么您可能已经很熟悉它们。 在本文中,我们不会涉及编程设计模式,也不会介绍元数据,甚至不会了解代理的工作方式。

Usually articles about traps always have the same examples to set private properties with proxies. It is a great example. However, here we are going to look at all the traps you can use. These examples are not meant to be real world use cases, the goal is to help you understand how Proxy traps work.

通常,有关陷阱的文章总是使用相同的示例来设置代理的私有属性。 这是一个很好的例子。 但是,这里我们将研究您可以使用的所有陷阱。 这些示例并非真实世界的用例,其目的是帮助您了解Proxy陷阱的工作方式。

陷阱? 什么? 听起来不祥 (Traps? What? It already sounds ominous)

I don’t really like the word trap. I’ve read everywhere that the word comes from the domain of operating systems (even Brendan Eich mentions it at JSConfEU 2010). However I am not exactly sure why. Maybe it’s because traps in the context of operating systems are synchronous and can interrupt the normal execution of the program.

我真的不喜欢陷阱这个词。 我到处都读到这个词来自操作系统领域(甚至Brendan Eich在JSConfEU 2010上也提到过)。 但是我不确定为什么。 可能是因为操作系统上下文中的陷阱是同步的,并且可以中断程序的正常执行。

Traps are internal method detection tools. Whenever you interact with an object, you are calling an essential internal method. Proxies allow you to intercept the execution of a given internal method.

陷阱是内部方法检测工具。 每当与对象交互时,您都在调用基本的内部方法 。 代理使您可以拦截给定内部方法的执行。

So when you run:

因此,当您运行时:

const profile = {};
profile.firstName = 'Jack';

You are telling your JavaScript engine to call the [[SET]] internal method. So the set trap will call a function to execute before profile.firstName is set to 'Jack'.

您正在告诉JavaScript引擎调用[[SET]]内部方法。 因此, set陷阱将在profile.firstName设置为'Jack'之前调用一个函数执行。

const kickOutJacksHandler = {
  set: function (target, prop, val) {
    if (prop === 'firstName' && val === 'Jack') {
      return false;
    }
    target[prop] = val;
    return true;
  }
}

Here our set trap will reject any program which tries to create a profile with the first name Jack.

在这里,我们的set陷阱将拒绝任何尝试创建名字为Jack的配置文件的程序。

const noJackProfile  = new Proxy ({}, kickOutJacksHandler);
noJackProfile.firstName = 'Charles';
// console will show {} 'firstName' 'Charles'
// noJackProfile.firstName === 'Charles'
//This won't work because we don't allow firstName to equal Jack

newProfileProxy.firstName = 'Jack';
// console will show {firstName: 'Charles'} 'firstName' 'Charles'
// noJackProfile.firstName === 'Charles'

我可以代理什么? (What can I Proxy?)

Anything that satisfies:

任何满足的条件:

typeof MyThing === 'object'

This means arrays, functions, object and even…

这意味着数组,函数,对象甚至……

console.log(typeof new Proxy({},{}) === 'object')
// logs 'TRUE' well actually just true... I got a bit excited...

PROXIES! You just can’t proxy anything if your browser doesn’t support it since there are no fully functional polyfills or transpiling options (more on that in another post).

代理! 如果您的浏览器不支持任何东西,那么您就无法代理任何东西,因为没有功能齐全的polyfills或transpiling选项(有关更多信息,请参阅另一篇文章)。

所有代理陷阱 (All the Proxy Traps)

There are 13 traps in JavaScript! I chose not to classify them, I’ll present them from what I think are the most useful to less useful (sort of). It’s not an official classification and feel free to disagree. I am not even convinced by my own ranking.

JavaScript中有13个陷阱! 我选择不对它们进行分类,而是从我认为最有用的到不有用的(某种)来介绍它们。 这不是官方分类,请随时不同意。 我什至不相信自己的排名。

Before we get started, here is a little cheat sheet taken from the ECMAScript specification:

在开始之前,这里有一些摘录自ECMAScript规范 :

Internal MethodHandler Method
[[Get]]get
[[Delete]]deleteProperty
[[OwnPropertyKeys]]ownKeys
[[HasProperty]]has
[[Call]]apply
[[DefineOwnProperty]]defineProperty
[[GetPrototypeOf]]getPrototypeOf
[[SetPrototypeOf]]setPrototypeOf
[[IsExtensible]]isExtensible
[[PreventExtensions]]preventExtensions
[[GetOwnProperty]]getOwnPropertyDescriptor
[[Enumerate]]enumerate
[[Construct]]construct
内部方法 处理程序方法
[[得到]] 得到
[[删除]] deleteProperty
[[OwnPropertyKeys]] ownKeys
[[HasProperty]]
[[呼叫]] 应用
[[DefineOwnProperty]] defineProperty
[[GetPrototypeOf]] getPrototypeOf
[[SetPrototypeOf]] setPrototypeOf
[[可扩展] 可扩展
[[PreventExtensions]] preventExtensions
[[GetOwnProperty]] getOwnPropertyDescriptor
[[枚举]] 枚举
[[构造]] 构造

获取,设置和删除:超级基础 (Get, Set and Delete: The super basic)

We already saw set, let’s take a look at get and delete. Side note: when you use set or delete you have to return true or false to tell the JavaScript engine if the key should be modified.

我们已经看过set ,让我们看一下getdelete 。 注意:使用setdelete ,必须返回truefalse来告诉JavaScript引擎是否应修改密钥。

const logger = []

const loggerHandler = {
  get: function (target, prop) {
    logger.push(`Someone  accessed '${prop}' on object ${target.name} at ${new Date()}`);
    return target[prop] || target.getItem(prop) || undefined;
  },
}

const secretProtectorHandler = {
  deleteProperty: function (target, prop) {
    // If the key we try to delete contains to substring 'secret' we don't allow the user to delete it
    if (prop.includes('secret')){
      return false;
    }
    return true;
  }
};

const sensitiveDataProxy = new Proxy (
  {name:'Secret JS Object', secretOne: 'I like weird JavaScript Patterns'},
  {...loggerHandler, ...secretProtectorHandler}
);

const {secretOne} = sensitiveDataProxy;
//logger = ['Someone tried to accessed 'secretOne' on object Secret JS Object at Mon Dec 09 2019 23:18:54 GMT+0900 (Japan Standard Time)']

delete sensitiveDataProxy.secretOne;
// returns false it can't be deleted!

// sensitiveDataProxy equals  {name: 'Secret JS Object', secretOne: 'I like weird JavaScript Patterns'}

玩键 (Playing With Keys)

Let’s say we have a web server that gets some application data to our route. We want to keep that data in our controller. But maybe we want to make sure it doesn’t get misused. The ownKeys trap will activate once when we try to access the object’s keys.

假设我们有一个Web服务器,它将一些应用程序数据发送到我们的路由。 我们希望将这些数据保留在我们的控制器中。 但是也许我们想确保它不会被滥用。 当我们尝试访问对象的键时, ownKeys陷阱将激活一次。

const createProxiedParameters  = (reqBody, allowed) => {
  return new Proxy (reqBody, {
    ownKeys: function (target) {
      return Object.keys(target).filter(key => allowed.includes(key))
    }
  });
};

const allowedKeys = ['firstName', 'lastName', 'password'];

const reqBody = {lastName:'Misteli', firstName:'Jack', password:'pwd', nefariousCode:'MWUHAHAHAHA'};

const proxiedParameters = createProxiedParameters(reqBody, allowedKeys);

const parametersKeys =  Object.keys(proxiedParameters)
// parametersKeys equals ["lastName", "firstName", "password"]
const parametersValues = parametersKeys.map(key => reqBody[key]);
// parameterValues equals ['Misteli', 'Jack', 'pwd']

for (let key in proxiedParameters) {
  console.log(key, proxiedParameters[key]);
}
// logs:
// lastName Misteli
// firstName Jack
// password pwd

// The trap will also work with these functions
Object.getOwnPropertyNames(proxiedParameters);
// returns ['lastName', 'firstName', 'password']
Object.getOwnPropertySymbols(proxiedParameters);
// returns []

In a real application you should NOT clean your parameters like this. However, you can build a more complex system based on proxies.

在实际的应用程序中,您不应像这样清理参数。 但是,您可以基于代理构建更复杂的系统。

数组中的重载 (Overloading in Arrays)

Have you always dreamt of using the in operator with arrays, but have always been too shy to ask how?

您是否一直梦想着将in运算符与数组一起使用,但总是害羞地问如何?

function createInArray(arr) {
  return new Proxy(arr, {
    has: function (target, prop) {
      return target.includes(prop);
    }
  });
};

const myCoolArray  =  createInArray(['cool', 'stuff']);
console.log('cool' in myCoolArray);
// logs true
console.log('not cool' in myCoolArray);
// logs false

The has trap intercepts methods which attempts to check if a property exists in an object using the in operator.

has trap拦截方法尝试使用in运算符检查对象中是否存在属性。

通过Apply控制功能调用率 (Control Function Call Rate with Apply)

apply is used to intercept function calls. Here we’re going to look at a very simple caching proxy.

apply用于拦截函数调用。 在这里,我们将看一个非常简单的缓存代理。

The createCachedFunction takes a func argument. The ‘cachedFunction’ has an apply (aka [[Call]]) trap which is called every time we run cachedFunction(arg). Our handler also has a cache property which stores the arguments used to call the function and the result of the function. In the [[Call]] / apply trap we check if the function was already called with that argument. If so, we return the cached result. If not we create a new entry in our cache with the cached result.

createCachedFunction采用func参数。 'cachedFunction'具有一个apply (aka [[Call]] )陷阱,每次我们运行cachedFunction(arg)时都会调用该陷阱。 我们的处理程序还具有一个cache属性,该属性存储用于调用函数的参数和函数的结果。 在[[Call]] / apply陷阱中,我们检查是否已使用该参数调用了函数。 如果是这样,我们将返回缓存的结果。 如果没有,我们将在缓存中创建一个带有缓存结果的新条目。

This is not a complete solution. There are a lot of pitfalls. I tried to keep it short to make it easier to understand. Our assumption is that the function input and output are a single number or string and that the proxied function always returns the same output for a given input.

这不是一个完整的解决方案。 有很多陷阱。 我试图使其简短以使其更易于理解。 我们的假设是,函数输入和输出是单个数字或字符串,并且代理函数对于给定的输入始终返回相同的输出。

const createCachedFunction = (func) => {
  const handler = {
    // cache where we store the arguments we already called and their result
    cache : {},
    // applu is the [[Call]] trap
    apply: function (target, that, args) {
      // we are assuming the function only takes one argument
      const argument = args[0];
      // we check if the function was already called with this argument
      if (this.cache.hasOwnProperty(argument)) {
        console.log('function already called with this argument!');
        return this.cache[argument];
      }
      // if the function was never called we call it and store the result in our cache
      this.cache[argument] = target(...args);
      return this.cache[argument];
    }
  }
  return new Proxy(func, handler);
};

// awesomeSlowFunction returns an awesome version of your argument
// awesomeSlowFunction resolves after 3 seconds
const awesomeSlowFunction = (arg) => {
  const promise = new Promise(function(resolve, reject) {
    window.setTimeout(()=>{
      console.log('Slow function called');
      resolve('awesome ' + arg);
      }, 3000);
    });
  return promise;
};

const cachedFunction = createCachedFunction(awesomeSlowFunction);

const main = async () => {
  const awesomeCode = await cachedFunction('code');
  console.log('awesomeCode value is: ' + awesomeCode);
  // After 3 seconds (the time for setTimeOut to resolve) the output will be :
  // Slow function called
  //  awesomeCode value is: awesome code

  const awesomeYou = await cachedFunction('you');
  console.log('awesomeYou value is: ' + awesomeYou);
    // After 6 seconds (the time for setTimeOut to resolve) the output will be :
  // Slow function called
  //  awesomeYou value is: awesome you

  // We are calling cached function with the same argument
  const awesomeCode2 = await cachedFunction('code');
  console.log('awesomeCode2 value is: ' + awesomeCode2);
  // IMMEDIATELY after awesomeYou resolves the output will be:
  // function already called with this argument!
  // awesomeCode2 value is: awesome code
}

main()

This is a bit tougher to chew than the other code snippets. If you don’t understand the code try copy/pasting it in your developer console and add some console.log() or try your own delayed functions.

这比其他代码片段要难一些。 如果您不理解该代码,请尝试将其复制/粘贴到开发人员控制台中,然后添加一些console.log()或尝试自己的延迟功能。

定义属性 (DefineProperty)

defineProperty is really similar to set, it’s called whenever Object.defineProperty is called, but also when you try to set a property using =. You get some extra granularity with an additional descriptor argument. Here we use defineProperty like a validator. We check that new properties are not writeable or enumerable. Also we modify the defined property age to check that the age is a number.

defineProperty实际上类似于set ,它在调用Object.defineProperty被调用,但是当您尝试使用=设置属性时Object.defineProperty被调用。 使用附加的descriptor参数可以获得更多的粒度。 在这里,我们将defineProperty用作验证器。 我们检查新属性是否不可写或不可枚举。 另外,我们修改定义的属性age以检查年龄是否为数字。

const handler = {
  defineProperty: function (target, prop, descriptor) {
    // For some reason we don't accept enumerable or writeable properties 
    console.log(typeof descriptor.value)
    const {enumerable, writable} = descriptor
    if (enumerable === true || writable === true)
      return false;
    // Checking if age is a number
    if (prop === 'age' && typeof descriptor.value != 'number') {
      return false
    }
    return Object.defineProperty(target, prop, descriptor);
  }
};

const profile = {name: 'bob', friends:['Al']};
const profileProxied = new Proxy(profile, handler);
profileProxied.age = 30;
// Age is enumerable so profileProxied still equals  {name: 'bob', friends:['Al']};

Object.defineProperty(profileProxied, 'age', {value: 23, enumerable: false, writable: false})
//We set enumerable to false so profile.age === 23

构造 (Construct)

apply and call are the two function traps. construct intercepts the new operator. I find MDN’s example on function constructor extension really cool. So I will share my simplified version of it.

apply和call是两个函数陷阱。 construct拦截new运算符。 我发现MDN关于函数构造函数扩展的示例非常酷。 因此,我将分享其简化版本。

const extend = (superClass, subClass) => {
  const handler = {
    construct: function (target, args) {
      const newObject = {}
      // we populate the new object with the arguments from
      superClass.call(newObject, ...args);
      subClass.call(newObject, ...args);
      return newObject;
    },
  }
  return  new Proxy(subClass, handler);
}

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

const Boy = extend(Person, function(name, age) {
  this.age = age;
  this.gender = 'M'
});

const Peter = new Boy('Peter', 13);
console.log(Peter.gender);  // 'M'
console.log(Peter.name); // 'Peter'
console.log(Peter.age);  // 13

不要告诉我该怎么办! (Don’t Tell Me What to Do!)

Object.isExtensible checks if we can add property to an object and Object.preventExtensions allows us to prevent properties from being added. In this code snippet we create a trick or treat transaction. Imagine a kid going to a door, asking for treats but he doesn’t know what’s the maximum amount of candy he can get. If he asks how much he can get, the allowance will drop.

Object.isExtensible检查我们是否可以向对象添加属性,而Object.preventExtensions允许我们阻止添加属性。 在此代码段中,我们创建了一个捣蛋交易。 想象一下一个孩子要进门寻求点心,但他不知道自己能得到的最大糖果量是多少。 如果他问他能得到多少,津贴将减少。

function createTrickOrTreatTransaction(limit) {
  const extensibilityHandler = {
    preventExtensions:  function (target) {
      target.full = true;
      // this will prevent the user from even changing the existing values
      return  Object.freeze(target);
    },
    set:  function (target, prop, val) {
      target[prop] = val;
      const candyTotal = Object.values(target).reduce((a,b) => a + b, 0) - target.limit;

      if (target.limit - candyTotal <= 0) {
        // if you try to cheat the system and get more that your candy allowance, we clear your bag
        if (target.limit - candyTotal < 0 )
          target[prop] = 0;
        // Target is frozen so we can't add any more properties

        this.preventExtensions(target);
      }  
    },
    isExtensible: function (target) {
      // Kids can check their candy limit 
      console.log( Object.values(target).reduce((a,b) => a + b, 0) - target.limit);
      // But it will drop their allowance by one
      target.limit -= 1;
      // This will return the sum of all our keys
      return Reflect.isExtensible(target);
    }
  }
  return new Proxy ({limit}, extensibilityHandler);
};

const candyTransaction = createTrickOrTreatTransaction(10);

Object.isExtensible(candyTransaction);
// console will log 10
// Now candyTransaction.limit = 9

candyTransaction.chocolate  = 6;

// The candy provider got tired and decided to interrupt the negotiations early
Object.preventExtensions(candyTransaction);
// now candyTransaction equals to {limit: 9, chocolate: 6, full: true}

candyTransaction.chocolate = 20;
//  candyBag equals to {limit: 9, chocolate: 6, full: true}
// Chocolates did not go change to 20 because we called freeze in the preventExtensions trap

const secondCandyTransaction = createTrickOrTreatTransaction(10);

secondCandyTransaction.reeses = 8;
secondCandyTransaction.nerds = 30;
// secondCandyTransaction equals to {limit: 10, reeses: 8, nerds: 0, full: true}
// This is because we called preventExtensions inside the set function if a kid tries to shove in extra candies

secondCandyTransaction.sourPatch = 30;
// secondCandyTransaction equals to {limit: 10, reeses: 8, nerds: 0, full: true}

GetOwnPropertyDescriptor (GetOwnPropertyDescriptor)

Wanna see something weird?

想看到奇怪的东西吗?

let candies = new Proxy({}, {
  // as seen above ownKeys is called once before we iterate
  ownKeys(target) {
    console.log('in own keys', target);
    return ['reeses', 'nerds', 'sour patch'];
  },
// on the other end getOwnPropertyDescriptor at every iteration
  getOwnPropertyDescriptor(target, prop) { 
    console.log('in getOwnPropertyDescriptor', target, prop);
    return {
      enumerable: false,
      configurable: true
    };
  }
});

const candiesObject = Object.keys(candies);
// console will log:
// in own keys {}
// in getOwnPropertyDescriptor {} reeses
// in getOwnPropertyDescriptor {} nerds
// in getOwnPropertyDescriptor {} sour patch
// BUT ! candies == {} and candiesObject == []

This is because we set enumerable as false. If you set enumerable to true then candiesObject will be equal to ['reeses', 'nerds', 'sour patch'].

这是因为我们将枚举设置为false。 如果将enumerable设置为truecandiesObject将等于['reeses', 'nerds', 'sour patch']

原型获取和设置 (Prototype Get and Set)

Not sure when this will come in handy. Not even sure when setPrototypeOf comes handy but here it goes. Here we use the setPrototype trap to check if the prototype of our object has been tampered with.

不知道什么时候会派上用场。 甚至不确定setPrototypeOf何时会派上用场,但是事情就到这里了。 在这里,我们使用setPrototype陷阱来检查对象的原型是否已被篡改。

const createSolidPrototype = (proto) => {
  const handler = {
    setPrototypeOf: function (target, props) {
      target.hasBeenTampered = true;
      return false;
    },
    getPrototypeOf: function () {
      console.log('getting prototype')
    },
    getOwnProperty: function() {
      console.log('called: ' + prop);
      return { configurable: true, enumerable: true, value: 10 };
    }
  };
};

枚举 (Enumerate)

Enumerate allowed us to intercept the for...in, but unfortunately we can’t use it since ECMAScript 2016. You can find more about that decision in this TC39 meeting note.

枚举允许我们拦截for...in ,但是很遗憾,自ECMAScript 2016起,我们无法使用它。您可以在TC39会议记录中找到有关该决定的更多信息。

I tested a script on Firefox 40 just so that you don’t say I lied to you when I promised 13 traps.

我在Firefox 40上测试了一个脚本,只是为了当您答应13个陷阱时您不会说我对您撒谎。

const alphabeticalOrderer = {
  enumerate: function (target) {
    console.log(target, 'enumerating');
    // We are filtering out any key that has a number or capital letter in it and sorting them
    return Object.keys(target).filter(key=> !/\d|[A-Z]/.test(key)).sort()[Symbol.iterator]();
  }
};

const languages = {
  france: 'French',
  Japan: 'Japanese',
  '43J': '32jll',
  alaska: 'American'
};

const languagesProxy = new Proxy (languages, alphabeticalOrderer);

for (var lang in languagesProxy){
  console.log(lang);
}
// console outputs:
// Object { france: 'French', japan: 'Japanese', 43J: '32jll', alaska: 'American' } enumerating
// alaska
// france

// Usually it would output
// france
// Japan
// 43J
// alaska

You might have noticed that we don’t use `Reflect` to make things simpler. We will cover reflect in another post. It the meantime I hope you had fun. We will also build a practical software to get a bit more hands-on next time.

您可能已经注意到,我们没有使用`Reflect`来简化事情。 我们将在另一篇文章中介绍reflect 。 同时,我希望你玩得开心。 我们还将构建实用的软件,以在下次更多操作。

table { width: 100%; } table.color-names tr th, table.color-names tr td { font-size: 1.2rem; } <p> table { border-collapse: collapse; border-spacing: 0; background: var(–bg); border: 1px solid var(–gs0); table-layout: auto; margin: 0 auto } table thead { background: var(–bg3) } table thead tr th { padding: .5rem .625rem .625rem; font-size: 1.625rem; font-weight: 700; color: var(–text-color) } table tr td, table tr th { padding: .5625rem .625rem; font-size: 1.5rem; color: var(–text-color); text-align: center } table tr:nth-of-type(even) { background: var(–bg3) } table tbody tr td, table tbody tr th, table thead tr th, table tr td { display: table-cell; line-height: 2.8125rem }

表{宽度:100%; } table.color-names tr th,table.color-names tr td {font-size:1.2rem; } <p>表{border-collapse:折叠; 边框间距:0; 背景:var(–bg); 边框:1px实心var(–gs0); 表格布局:自动; margin:0 auto} table thead {背景:var(–bg3)} table thead tr th {填充:.5rem .625rem .625rem; 字体大小:1.625rem; 字体粗细:700; 颜色:var(–text-color)}表tr td,表tr th {填充:.5625rem .625rem; 字体大小:1.5rem; 颜色:var(–text-color); text-align:center} table tr:nth-​​of-type(even){background:var(–bg3)} table tbody tr td,table tbody tr th,table thead tr th,table tr td {display:table-cell ; 行高:2.8125rem}

翻译自: https://www.digitalocean.com/community/tutorials/js-proxy-traps

javascript 代理


http://www.niftyadmin.cn/n/3648935.html

相关文章

Android 系统Action大全

常用Action说明&#xff1a; String ADD_SHORTCUT_ACTION 动作&#xff1a;在系统中添加一个快捷方式。. “android.intent.action.ADD_SHORTCUT”String ALL_APPS_ACTION 动作&#xff1a;列举所有可用的应用。 输入&#xff1a;无。 “android.intent.action.ALL_APPS”Strin…

23种设计模式之中介者模式

中介者模式本质 解耦多个部门对象之间的交互关系。每个对象都持有中介者对象的引用&#xff0c;只跟中介者对象打交道。我们通过中介者对象统一管理这些交互关系 android最常见的场景 MVC模式(其中的C)&#xff0c;控制器就是一个中介者对象。M和V都和它打交道 总经理接口:…

gatsby_使用gatsby-awesome-pagination在Gatsby中进行分页

gatsbyDespite Gatsby’s amazing performance, it would be best not to force the user to load every single post onto the same page. Instead, we’ll explore the gatsby-awesome-pagination plugin to segment our post archive into more manageable sections. 尽管Ga…

[收藏]伟大架构师的秘密

伟大架构师的秘密发布日期&#xff1a; 10/13/2004| 更新日期&#xff1a; 10/13/2004By Don Awalt and Rick McUmberRDA Corporationhttp://www.microsoft.com/china/msdn/library/architecture/architecture/architecturetopic/USdnmajgreatarchitect.mspx摘要&#xff1a;所…

23种设计模式之命令模式

定义:将一个请求封装为一个对象&#xff0c;从而使我们可用不同的请求对客户进行参数化&#xff1b;对请求排队或者记录请求日志&#xff0c;以及支持可以撤销的操作&#xff0c;也称之为&#xff1a;动作Action模式&#xff0c;事务transaction模式。 结构&#xff1a; Comm…

Genymotion常见问题整合与解决方案

为什么说是常见问题整合呢&#xff0c;因为我就是Genymotion最悲剧的使用者&#xff0c;该见过的问题&#xff0c;我基本都见过了&#xff0c;在此总结出这血的教训&#xff0c;望大家不要重蹈覆辙。常见问题1&#xff1a;Genymotion在开启模拟器时卡在了starting virtual devi…

day.js使用_使用Day.js解析,验证,处理和显示JavaScript中的日期和时间

day.js使用With it’s last release nearly a year ago, the most recent commit over 6 months ago, and hundreds of open bugs and pull requests, it’s starting to seem like Moment.js is slowing down and it’s time to shop for more actively maintained alternativ…

23种设计模式之解释器模式

介绍 是一种不常用的设计模式用于描述如何构成一个简单的语言解释器&#xff0c;主要用于使用面向对象语言开发的编译器和解释器设计。当我们需要开发一种新的语言时&#xff0c;可以考虑使用解释器模式。 开发中常见的场景 EL表达式式的处理正则表达式解释器。SQL语法的计数…