董懂 发布的文章

async 函数适合执行一些流程,不要试图使用 async 函数返回一些东西。有一个错误的例子就是在Vue的模板中绑定一个计算属性,这个计算属性里面使用 async 函数获取数据。示例代码如下:

HTML模板部分:

<div>{{computedValue}}</div>

JavaScript部分:

//......
computed: {
    async computedValue(){
        let val = await aAsyncMethod();
        return val;
    }
}

如果你这么做了,你永远只会得到如下字符串:

'[object Promise]'

为什么?

原因很简单,就像 MDN 上这篇文档 MDN - async函数 介绍的,async 遇到await的时候会立即返回一个Promise,把控制权交出,直到 await 后面的异步操作有结果(resolve或者reject)才会继续执行后面的代码。可以说,async 函数的返回值永远是一个Promise

END

有时候我们需要从后端接口返回的二进制流数据中读取文件内容。在控制台中查看二进制流,会看到下面这样的东西:

1.jpg

这就需要前端读取二进制数据。

使用fileReader

如果你要将获取到的流显示出来,比如一张图片,那么一般都是通过将二进制流转换为blob,然后使用fileReader读取DataURL,并将它赋值给imgsrc

axios和请求一个图片为例。
首先,如果请求获取的是二进制数据,需要设置请求类型:

request({
    //...
    responseType: 'arraybuffer'
    //...
})

获取到数据后,将接口返回的数据转换成blob

request({...}).then(r => {
    let myblob = new Blob([r], { type: 'image/*' })
})

接着,使用fileReader读取blob

request({...}).then(r => {
    let myblob = new Blob([r], { type: 'image/*' });
    let reader = new FileReader();
    reader.readAsDataURL(myblob);
    reader.onload = function () {
        console.log(reader.result);
    };
})

以上代码将会打印出下面这种字符:

data:base64; image/*, foiHirf89hHoheruiefhi......

这样的Data URL可以直接用在imgsrc属性上。

fileReader还提供了readAsArrayBuffer()readAsText()等方法,用来读取不同类型的内容。

使用URL.createObjectURL

如果你想把流下载下来,那么使用此方法比较合适。
使用URL.createObjectURL(blob)可以创建一个指向blob内容的url。获取blob的步骤和之前一样,获取到 blob 后,使用下面的代码创建 ObjectURL:

const blob = ......;
const url = URL.createObjectURL(blob);
//生成的url如下:
'blob:http://localhost:9999/0a147e01-97a4-482a-8aa3-e44a2bf79478''

之后,需要借助adownload属性,下载这个文件:

var link = document.createElement('a');
link.href = url;
link.download = 'fileName.jpg';
document.body.append(link);
link.click();
link.remove();

此时文件已经自动下载了。注意,adownload属性要设置正确的带扩展名的文件名,这样才能正确下载文件,而不必设置文件的mime
最后,别忘了调用如下方法释放资源:

URL.revokeObjectURL(url);

END

我是不是相亲相爱一家人朋友圈看多了,竟然起了个这样的标题……咳咳,言归正传

其实这种情况只存在嵌套循环中。

下面两个数组中,obj1某一项的id等于obj2某一项的xid,想要通过循环把obj2每一项的value赋给obj1中与之对应的项:

const obj1 = [
  { id: 1, ... },
  ...
];

const obj2 = [
  { xid: 1, value: 1, ...},
  ...
]

不知道为什么,下面这种写法,外层循环只能循环到i = 3就不往下执行了:

let i = 0, j = 0, lenI = obj1.length, lenJ = obj2.length;

for (; i < lenI; i++) {
  for (; j < lenJ; j++) {
    if (obj1[i]['id'] === obj2[j]['xid']) {
      obj1[i]['value'] = obj2[j]['value'];
      break;
    }
  }
}

下面这样写就没问题:

for (let i = 0, len_i = obj1.length; i < len_i; i++) {
  for (let j = 0, len_j = obj2.length; j < len_j; j++) {
    if (obj1[i]['id'] === obj2[j]['xid']) {
      obj1[i]['value'] = obj2[j]['value'];
      break;
    }
  }
}

为啥呢?原因很简单,当内层循环执行的时候,j的值已经改变了,前几次循环因为触发了breakj的值还小于lenJ,所以还能继续执行,直到j == lenJ,内层循环就不会再执行了。

这个错误好愚蠢……

END

众所周知,<a>href属性除了能指向一个URL,还能指向锚点。锚点链接就不说了,这里说说它的特殊用法。

在初期开发,尤其是切图的时候,需要给a一个href属性,否则a可能会没有应有的样式,例如没有下划线,看起来就跟普通文本无异。如果仅仅这样写:

<a href>link</a>

又相当于

<a href="当前页面的URL"></a>

点击之后会刷新当前页面。
好多开发者喜欢写上一个#来表示这是一个空链接:

<a href="#">link</a>

这样倒是没有上面的问题,但又有新问题:页面会跳转到顶部。很多大网站都没有注意到这一点。
其实解决这个问题的方法很简单,只需要在#后面再添加一个不存在的锚点,点击a的时候就没有任何反应了:

<a href="#idonotcare">link</a>

简洁点也可以这样写:

<a href="##">link</a>
<a href="#?">link</a>

全凭你的喜欢好了。

不过这样也有点美中不足:点击之后,虽然页面没有反应,但会在浏览器的地址栏出现你设置的锚点。其实这无关紧要。不过如果你非要觉得别扭,那下面的方法可以解决这个小小的不足:

<a href="javascript:void(0)">link</a>

void总会返回undefined,你在void之后写啥都没关系。这样写除了要多输入几个字符,就没什么问题了。

但是,在鼠标放到这个链接上时,浏览器下面还是会显示这个链接的地址:javascript:void(0)。比如在Chrome中,对于追求完美的你来说,显示地址的小白条会破坏页面的整体感,这时候,你就只好用设置a的默认样式+去掉href属性+绑定click的方法了……

如果你想这么写也不是不可以:

<a href="javascript:undefined">link</a>
<a href="javascript:false">link</a>
<a href="javascript:1">link</a>
<a href="javascript:1+1">link</a>
<a href="javascript:function f(){}">link</a>

但是这样用要小心!虽然在一些浏览器里毫无反应,但是在Firefox里,页面会被表达式的值替换
至于类似下面这样的,千万别用就对了:

<a href="javascript:document.write('0')">link</a>
<a href="javascript:document.title">link</a>

如果你好奇地试了,那你的页面就会被替换成0或者网页标题。

END

1、如何使用

很多场景,比如给ajax请求传递参数时,参数往往是一个对象,对象的属性个数不是固定的,需要动态添加:

let params = {
    id: 1
};

if(name){
    params.name = name;
}

if(address){
    params.address= address;
}

现在ES6提供了扩展运算符,以上的代码可以改成下面这样:

let params = {
    id: 1,
    ...(name && {name}),
    ...(address && {address}),
}

如果nameaddress的值都存在且不是空字符串,结果就会是这样:

{
    id: 1,
    name: 'dong',
    address: '赤水沟子'
}

如果它们的值为,就会不存在nameaddress这两个属性:

{
    id: 1
}

2、原理

下面以name为例,说明它的原理。

如果name不为空,则

name && {name}

相当于

name && {name: name} //上面是ES6简写形式

相当于

if(name){
    return {
        name: name
    }
}else{
    return false;
}

如果name没有值,那么

...(name && {name})

相当于

...(false)

扩展运算符有以下规定:

1、如果扩展运算符后面是一个空对象,则没有任何效果
2、如果扩展运算符后面不是对象,则会自动将其转为对象

根据第2点,

...(false)

等同于

...Object(false)

等同于

...Boolean {false}

Boolean {false}没有任何属性,所以又等同于

...{}

根据第1点可以知道,这没有任何效果,不会向params添加任何属性。

END

一个程序员经常脖子疼,他每天的工作就是敲键盘写代码和查资料。今天他突然发现大多数网页的主体内容都在屏幕左侧,他坐在屏幕中央时,不得不歪着脖子看屏幕,这导致他的脖子经常酸痛。在写代码的时候他又发现,键盘的主按键区也在键盘的左侧,他把键盘摆在正中央的时候,不得不歪着身子码字,本来酸痛不已的脖子感觉好像滑丝了,摇摇欲坠,不太好控制了。

就在他写这些字的时候,他也是歪着脖子,忍受着脖子疼写的,因为编辑器窗口在屏幕最左侧。

经常看到这样的JavaScript代码:

var o = {
    f: function(){
        console.log(this);
    }
}

(0, o.f)();

这样做的目的是改变f的执行环境,也就是改变this。

直接调用o.f:

o.f();
//{f: ƒ}

再试试间接调用:

(0, o.f)();
//Window...

可以看出this已经是Window了。

逗号运算符会从左往右求值,并返回最后一个表达式的值,(0, o.f)返回的是一个函数,此时这个函数的作用域是全局,相当于:var f = o.f
必须要用0吗?其实任何表达式都可以:

(1, o.f);
(1+1, o.f);
(true, o.f);

()是不是必须的呢?要调用返回的函数,或者将返回的函数赋值给某个变量,括号就是必须的。下面的代码肯定是不能正确运行的:

0, o.f(); //this指向o
var f = 0, o.f //Unexpected token '.'

我们需要()来将语句转换为表达式,这也是经常用到的方法,比如下面的代码,需要将x = 2这条语句转换为表达式:

true && (x = 2)

END

JavaScript获取时间戳有多种方式。这里有一个具有纪念意义的时间:

var now = new Date('2022/02/02 22:22:22:222');
//Wed Feb 02 2022 22:22:22 GMT+0800 (中国标准时间)

我们想看看它的时间戳是不是也如此独特。下面是如何获取它的时间戳。

方法1:Dateparse方法

Date.parse(now);
//1643811742000

Date.parse接受一个表示时间的字符串,所以这里的now先转换成了字符串Wed Feb 02 2022 22:22:22 GMT+0800 (中国标准时间),然后再被解析成时间戳。因为每个浏览器实现它的方式不一致,所以建议不要用Date.parse解析时间字符串。见:Date.parse() - MDN
要注意的是,此方法返回的毫秒数都是0.

方法2:Date实例getTime方法

now.getTime();
//1643811742222

这个方法实际上跟方法1是等价的,方法1是显式调用,方法2是隐式调用,但此方法返回正常的毫秒数。

方法3:Date实例的valueOf方法

now.valueOf();
//1643811742222

这个方法返回一个Date对象的原始值。Date对象的原始值是基于Unix Time Stamp,即自1970年1月1日(UTC)起经过的毫秒数。

方法4:+操作符获取时间戳

+now;
//1643811742222

这个方法实际上调用的是now.valueOf()
+运算符在这里是一元运算符,会将后面的操作数转换成数字,等于Number()函数。数字是一种原始值。在JavaScript内部,会调用toPrimitive把对象转换成原始值,形式如下:

toPrimitive(input,preferedType?)

preferedType可选numberstring+会将preferedType看作是number,将按照下面的规则转换:

  1. 如果input是原始值,直接返回这个值;
  2. 否则,如果input是对象,调用input.valueOf(),如果结果是原始值,返回结果;
  3. 否则,调用input.toString()。如果结果是原始值,返回结果;
  4. 否则,抛出错误。

+now按照规则2调用了now.valueOf()
注意,Date.prototype.valueOf重写了Object.valueOf,所以会返回一个时间戳。

结果证明now的时间戳并不独特:(

END

0、保存Map实例

NOTE:

如何引入天地图SDK请参考以前的文章

首先要做的肯定是保存Map实例,可以保存在Vue实例上:

this.Map = new T.Map(......);

或者保存在window上:

window.Map = new T.Map(......);

1、创建标注

创建标注使用的是工具类。下面都用点(Marker)来作为例子。

在页面中创建一个按钮,使用它的click事件来打开标注工具

<button @click="btnClick">添加点</button>

为了看起来不违和,你可以把这个按钮的样式做成和地图原生控件一样。

click事件中打开标注工具:

btnClick(){
  //如果标注工具已经存在,先关闭它
  window.marktool && window.marktool.close();

  //创建一个Icon实例
  let icon = new T.Icon({
    iconUrl: 'https://x.y.z/1.png'
  });

  //实例化工具
  let marktool = new T.MarkTool(window.Map, {
    icon,
    follow: true
  });

  marktool.open();
  window.marktool = marktool;
}

现在,点击添加点按钮的时候,鼠标指针将变成你定义的icon的样式,点击地图,就会在点击的位置留下一个标记。

2、给标记添加自定义的数据

标记肯定代表了某个实际的东西,需要补充额外的内容。可以在上一步给marktool注册一个mouseup事件,给刚刚创建的标记添加一个click事件,以便我们进行更多的操作:

//别忘了先保存一下Vue实例this:
let _this = this;
//onMarkMarked是Vue的Method
//e.currentMarker代表当前标记
marktool.addEventListener('mouseup', function (e) {
  _this.onMarkMarked(e.currentMarker);
});

VueonMarkMarked方法中:

onMarkMarked(marker){
  let _this = this;
  //直接添加数据
  marker.MyData = {foo: 'bar', baz: 'balabala...'};
  //保存marker,使页面中任何代码都能访问,就能任意赋值了
  //但要注意,currentMark保存的是当前标记,创建新标记时,它代表的是新的标记
  _this.currentMark = marker;
}

Vue的其他方法中给它赋值:

onModelClosed(){
  this.currentMark.myname = this.inputName;
  //......

  //可以在控制台看到marker实例中包含自定义的数据
  console.log(this.currentMark);
}

3、保存标记数据

首先获取到标记:

let markers = window.Map.getOverlays();

返回的是一个标记数组,如果有多个标记,根据需要,可能需要循环处理。这里假设只有一个marker标记。
标记信息含有很多无用的信息,我们需要的一般都是标记的坐标、自定义的数据。标记的样式由于是提前定义好的,所以并不会通过标记获取。

let lnglat = markers[0].getLnglat();
let { myname, MyData } = markers[0];

此时就可以保存到数据库了。


END

堵车是城市里的人几乎每天都要经历的事情,即使是身处经济一般的小县城,每天也要堵那么一段时间,赶上刮风下雨、中秋国庆,那必定会非常堵。

今天看到一则新闻,说是外国有的城市是无车城市,市区不允许开私家车。这样做效果肯定非常好,但是也有很多缺点,比如可能会降低城市运行效率,可能有许多特殊的人群必须要开车……我觉得这样还是太激进了。我有一个想法:城市中只允许开微型车。

每当我在路上看到一个人开着宽大的SUV或者中大型轿车,仅仅是一个人去上班的时候,我就觉得,这是不是太浪费了?即使是小型车,往往车长也有4米多,而大多数车只坐一个人。上下班拼车虽然能提高利用率,但又消耗额外的时间。实际上上下班通勤开微型车就足够了。五菱宏光mini ev就是一款非常好的车型,能够满足绝大多数人的需要。像Polo、飞度之类的,我都觉得有点长了。

说到这里我还有一些感想:为什么很多人对于一些事那么讲究呢?酒店要星级的,车要商务的……一个人真的需要这些东西吗?世界上绝大多数人的价值观是不是被少数人绑架了……