杂记随笔备忘

Posted by Youzi Blog on May 19, 2020

随手记录备忘

随手记录一些看到的点。

正篇

with 表达式

Q: 昨天看到个面试题,问with表达式的利弊,还问为什么Vue的编译函数里一直用到这个语法;

A1: with语句是一个用于扩展作用域链的语法,表达式语法:

1
2
3
with (expression) {
  statement;
}

大家都知道 JS 找变量的过程是顺着作用域链往上找的,with语句将某个对象expression添加到作用域链的顶部,如果statement中的未命名空间变量,和作用域链的属性同名,则这个变量就指向了同名属性。

利弊:

  • 性能层面:语句在不造成性能损失的情况下,减少变量长度,语句造成的附加计算很少,利用with可以减少不必要的指针路径解析运算。什么意思呢,就是平常我们访问对象属性时,经常有对象路径太长的情况,obj.prop1.child2.foo.bar这种情况,用with语法
1
2
3
with (obj.prop1.child2.foo) {
  console.log(bar);
}

事实上这样写就会造成性能问题了,写在大括号里的属性,都会在指定作用域中找一遍,如果没找到,那就再沿着本来的作用域链向上找一遍。

  • 语义层面:其实用with有不少 bug 的,而且语义不清晰;
1
2
3
4
5
6
7
8
9
10
11
12
13
let o = {};
with (o) {
  a = 10; // 本来写这个是想让o.a = 10,其实解释器执行的时候会认为在o里面找不到a,那就会在window上添加a;
}
o.a; // undefined
window.a; // 10

function f(foo, bar) {
  with (bar) {
    console.log(foo);
    // 这谁能知道会输出啥啊,如果bar有foo这个属性,就会输出bar.foo的值,如果没有,就会输出第一个参数foo
  }
}

所以可以看到自己平常写代码的时候尽量少点用with了,不能保证实际效果,可以直接用临时变量代替咯。let tmp = obj.prop1.child2.foo

A2: 那为什么在vue-template-compiler里面还大量用了这个表达式呢(with(this) {}),你还记得平常在template模板里面写 JS 的时候不用加作用域链限制前缀this之类的吗,这就是with的作用了。

重点:不需要在编译template的时候去分析模板里面的 JS 语句的作用域,直接交给with就能拥有正确的作用域。所以可以看出是不是在模板 JS 里面最好不要调用全局方法啊,不然在当前对象找不到该方法,还得去全局作用域链里面再找一次。

事实上,上述的这些特点都是在浏览器直接引入Vue.js时才存在的,我们知道既可以直接引入依赖包,也可以使用工程化的方案在构建时编译好;直接在浏览器环境引入的话,因为考虑到写个完整的AST会导致依赖包太大,会拖慢节奏;如果使用工程化构建,会使用完整的AST编译语句,就会剔除掉with,所以只是会导致打包编译的过程变慢,但最终生成的代码没有with

附上尤大在知乎的解答:如何看待 Vue.js 2.0 的模板编译使用了with(this)的语法? - 尤雨溪的回答 - 知乎

Vue 使用 window.onresize

一般我都避免使用的,想用其他方式解决,但实在不得不用的时候要注意,如果不在Vue里面,要记得使用防抖函数;在Vue里使用的话要记得使用$nextTick,不然一直被触发,耗性能;另外记得在当前页面(或组件)销毁时将该函数置为空,在下个页面大概率用不着,或者压根就会报错。

1
2
3
4
5
6
7
8
9
10
mounted() {
  window.onresize = () => {
    this.$nextTick(() => {
      // do something
    })
  }
}
beforeDestroy() {
  window.onresize = null
}

记录一个 node | npm 拒绝访问的问题

原因不多赘述了,直接看现象;版本如下,报错如下,另外 win 环境安装的是 64 位的

1
2
3
4
5
6
7
8
node -v
v14.5.0
npm -v
拒绝访问
6.14.5
npm run dev
拒绝访问
然后就是一堆报错信息

搜了下好像都说是权限问题,但是在 win 环境下用管理员权限,cmdpowershell都还是报错;最后咋解决的呢,换了个 32 位的.msi的安装包,至今费解,因为刚开始报错的时候我也重装过好几次node,还分别用了好几种用户类型去安装,最后装了个 32 位的,就成了,就很怪,仅做记录。

统计页面停留时间

通过监听路由变化(或判断变化的 url 是否为不同的页面),来统计停留时间,还需要减去窗口最小化的时间;

  • 监听路由变化(针对 SPA):
    • history 模式又分为两类:监听pushState | replaceState或监听popstatepopstate事件会在用户手动前进或者后退浏览器时被触发(手动调用history.go | back | forward也会触发);而pushState不会触发该事件,所以要结合起来用;
    • hash 模式直接监听hashchange事件即可;

pushState | replaceState方法的改写,使得能够触发我们需要的监听函数;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let _wr = function (type) {
  let orig = window.history[type];
  return function () {
    let rv = orig.apply(this, arguments);
    let e = new Event(type.toLowerCase());
    e.arguments = arguments;
    window.dispatchEvent(e);
    return rv;
  };
};
window.history.pushState = _wr('pushState');
window.history.replaceState = _wr('replaceState');
window.addEventListener('pushstate', function (event) {});
window.addEventListener('replacestate', function (event) {});

hashchange事件监听:

1
2
3
4
5
6
7
window.addEventListener(
  'hashchange',
  () => {
    // do something
  },
  false
);
  • 监听活跃状态的切换

通过page Visibility API或者可以监听window.onblur | onfocus事件;

1
2
3
document.addEventListener('visibilitychange', function (event) {
  console.log(document.hidden, document.visibilityState);
});
  • 何时上传数据到服务器

存储到本地缓存里,在下次打开时再上传;考虑到如果仅去监听window.onload | onbeforeunload方法,有可能请求还没发送完页面就关闭了;而存储本地的话耗时会短的多,本地存储时还需要加上一些时间戳,进入时间、离开时间等。

依赖注入相关

一般我们提及依赖注入,会想到在 Java 的 spring 框架中用的多;依赖注入其实是一种设计模式,简单来说可以总结为:当资源互相依赖时,我们可以把资源的管控权释放给一个全局的容器(我们一般称为 IOC),需要资源的时候去这个容器里面取就行了。

很多时候资源在代码里体现的是某个类,当类之间相互依赖时,我们就会把类丢到容器里(这个过程称为注入),需要的时候就由容器去实例化类得到类实例,返回给使用者;注意这里重点就是我们要把实例化类的工作交给容器,返回给使用者的只能是一个类的实例;

这是因为如果类 A 被多个类同时依赖,万一 A 要修改自身的定义(这种情况很常见),而我们把实例化的工作交给了 A 的使用者,那我们就要深入到使用者的代码里,去修改实例化的代码,换种思路如果我们把实例化的工作交给第三方,那这样修改的时候只要改第三方的代码即可;这就像是黑盒,对使用者来说,只要拿到实例就可以用了,不用关心类是怎么实现的。

由上述定义可以得出,在依赖注入设计模式里,至少有三个角色:容器(IOC),资源提供者(Provider),资源使用者(User);整个调用过程:provider 通过 IOC 的某个方法,将自身注册到 IOC 中,User 通过 IOC 拿到所需要的资源,完成调用;

eslint / prettier

知乎看到一篇感觉不错的,写的挺全的:使用 Prettier 统一格式化项目代码 - AnLi 的文章 - 知乎 https://zhuanlan.zhihu.com/p/87586114

项目中遇到了忽略某些格式化代码的问题,记录一下:

  • eslint 忽略代码

    • 对于路径或者某些文件,在项目根目录下建立.eslintignore文件,在文件里添加路径或者文件夹即可;
    • 文件顶部加入注释/* eslint-disable */可以忽略整个文件;
    • 忽略代码块可以用/* eslint-disable */ | /* eslint-enable */包裹起来;还可以添加参数,表示忽略某条具体的规则:/* eslint-disable no-alert, no-console */
    • 指定行:写在同一行:// eslint-disable-line;忽略下一行:// eslint-disable-next-line;同样可以添加参数来忽略某些规则;
  • prettier 忽略代码

    • eslint类似,忽略路径.prettierignore
    • 忽略行:// prettier-ignore