Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

巧用Proxy,实现指令解析 #16

Open
GeoffZhu opened this issue Jun 11, 2019 · 0 comments
Open

巧用Proxy,实现指令解析 #16

GeoffZhu opened this issue Jun 11, 2019 · 0 comments

Comments

@GeoffZhu
Copy link
Owner

这次的故事要从Vue1.x的模版语法开始,Vue模板中通过指令传递props,语法如下

<a :href="foo.bar[0].url">...</a>

指令的值(既foo.bar[0].url)可以是任意对象取值字符串,只要符合JavaScript语法既可。这个字符串会在模板编译阶段转化为render函数,其中的foo.bar[0].url会被解析成['foo', 'bar', '0', 'url']。每次render函数执行时,通过解析后的数组,一个循环就可以取到值了data['foo']['bar']['0']['url']

上周我们团队Coding,就是实现这样一个方法,输入对象取值字符串,解析成数组。 最后的结果可以说是百花齐放,很有意思。我自己收益也颇多,尤其是对Proxy,所以才分享出来。

foo.bar[0].url解析成['foo', 'bar', '0', 'url'],看似简单的对象字符串解析,如果要实现的健壮,也是要下一番功夫的。毕竟javascript对象取值的写法也不少。在Vue1.x中,为了解决对象路径字符串解析的问题,直接祭出了有限状态机,这才算满足了需求。

可以停一分钟思考下,你会怎么实现上面这个方法呢?

...

好了,先来看看一号选手,一行代码

/**
 * 获取子字符串
 * @param  {String} source - 字符串
 * @return - 路径数组
 */
function getPath(source) {
  return source.replace(/]|\'|\"/g, '').replace(/\[/g, '.').split('.');
}

先把 ] ' "都替换掉再把[替换成.,最后split下。虽然满足最最基本满足需求,但稍微特殊点字符串就跪了,例如a['b[']

二号选手,正则一哥,一个我看不懂的正则基本就全搞出来了。

const MATCH_PATH_EXP = /[^\.\[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
/**
 * 获取子字符串
 * @param  {String} source - 字符串
 * @return - 路径数组
 */
function getPath(source) {
  const pathArray = [];
  source.replace(MATCH_PATH_EXP, (match, number, quote, subString) => {
    if (quote) {
      pathArray.push(subString.replace(/\\(\\)?/g, '$1')); // 转义符号修正
    } else {
      pathArray.push(number || match); // 有数组匹配数字,没数字匹配字母
    }
  });
  return pathArray;
}

最后一个让我受益颇多的,同样非常简单,而且比正则性能好得多,Proxy。

/**
 * 获取子字符串
 * @param  {String} source - 字符串
 * @return - 路径数组
 */
function getPath(s) {
  const accessLog = [];
  const objName = 'obj';
  const obj = new Proxy({}, {
    get: (o, key) => {
      accessLog.push(key);

      return obj;
    }
  });

  try {
    eval(`${objName}.${s}`);
  } catch (e) {
    const msg = `Invalid object, ${e.message}`;

    throw new Error(msg);
  }

  return accessLog;
}

用Proxy先处理一起,然后直接eval执行传入的字符串,每次返回同一个Proxy。这相当于直接用JS引擎解析了字符串,代码只负责接收引擎解析出来的key就好了,妙~。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant