分类 技术文章 下的文章

啥也不说,直接上链接:https://developer.mozilla.org/zh-CN/docs/Web/API/structuredClone
如果你懒得点,那只要知道下面的东西就行了。

全局的 structuredClone() 方法使用结构化克隆算法将给定的值进行深拷贝,说人话就是使用了 JavaScript 内部的一种算法深拷贝对象。这个内部的方法其实也是递归,它有一些限制,不能拷贝一些东西,这些距离咱们码农很远,一般不用考虑。如果你很好奇,结构化克隆算法的介绍在这里:

结构化克隆算法:
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Structured_clone_algorithm

structuredClone 的语法如下:
structuredClone(value)
structuredClone(value, { transfer })

返回值就是你想要的那个东西:新对象。
可选参数 transfer 是啥?是可转移对象数组。啥是可转移对象数组?大概是跟内存有关的数据,例如 arrayBuffer 之类的,一般情况下也用不到。介绍在这里:

可转移对象:
https://developer.mozilla.org/zh-CN/docs/Glossary/Transferable_objects

structuredClone 示例如下:

let obj = { x: 1 };
let newObj = structuredClone(obj);
console.log(obj === newObj); //false

END

svg标签既能设置widthheight属性,又能设置viewBox属性,而svg作为一个 html 标签,也能在 css 或者 style 中设置widthheight。这三者的关系是什么?

1、只有viewBox属性

这里有一个 svg 制作的鸡蛋黄(有点黄,可能是一个咸鸡蛋黄),代码如下:

<style>
    #svg{
        background-color:antiquewhite;
    }
</style>

<svg id="svg" viewBox="0 0 200 200">
    <circle cx="100" cy="100" r="100" fill="darkorange"></circle>
</svg>

效果如下:

1.png

可以看到,半径是100的圆,填满了整个svg,因为svgviewBox的长宽都是200。而svg标签作为 html 元素,又填满了整个页面宽度,并不是200*200的尺寸。

下面我们改变一下viewBox的大小:
...viewBox="0 0 100 100"
效果是这样的:

1 (1).png

viewBox变大一些呢?
...viewBox="0 0 300 300"
效果是这样的:

1 (2).png

现在这个鸡蛋黄已经不能占满整个 svg “画布”了。如果修改鸡蛋黄的圆心坐标为viewBox的中心点,是不是会让鸡蛋黄居中呢?

<circle cx="150" cy="150"...>

确实居中了:

1 (3).png

看来圆的坐标系是viewBox

2、给 svg 标签添加widthheight属性

将代码恢复到最原始的样子,并进行下面的修改:

<svg height="100" width="100"...

变化如下:

1 (4).png

svg 作为 html 元素,长宽都变成了100px,但鸡蛋黄还是占满整个svg标签。看来svgwidthheight属性是用来控制svg作为 html 元素的宽高的,不会影响svg里面的形状等元素的大小。

3、设置 css 宽高

在上面代码的基础上,再添加下面的代码:

...
#svg{
    height: 300px;
    width: 300px;
    ...
}
...

这时候有了下面这种变化:

1.png

svg 元素的宽高变成了 css 中设置的300px

4、总结

根据上面的表现,可以得出下面的结论:

  1. 不提各种坐标,简单理解,可以将viewBox看作是svg画布大小。画布变大了,里面的形状当然就显得小了;如果画布变小了,就有可能将里面的形状裁切。这跟 Photoshop 中的图像 - 画布大小命令表现是一样的。
  2. svg 的widthheight属性设置的是svg这个 html元素 的大小。因为 svg 是矢量图形,所以 svg 的viewBox,包括viewBox中的内容会等比缩放,但并不会变形。当然,widthheight要和viewBox等比例,否则会出现裁切
  3. css 中的widthheight会覆盖 svg 标签本身的widthheight属性,也就是 css 的宽高具有更高的优先级。

在制作svg图形时,可以任意定义viewBox大小,并在其中放置各种形状,这些形状(包括文字等)的定位都相对viewBox。而svg实际应用后的大小,取决于widthheight属性,或者css样式。

是不是非常简单?


End

由于 v-if 在条件为 false 时,会销毁它内部的所有内容,当条件为 true 时,重新渲染内部的内容,所以 v-if 可以用作刷新某些区域用。

例子1

例如,input[type=file] 控件,如果你选择的文件跟上次选择的是同一个,它就不会触发 changeinput 事件,除非你选择一个新的文件。即使大多数情况下,这种情形并不需要特殊处理,因为用户不可能老是选择同一个文件,但总是会遇到特殊的时候。这个时候就可以用 v-if 来销毁原来的 file 控件,并渲染一个新 file,这个新的 file 上面绑定的数据、事件都跟原来一模一样:

<input type="file" v-if="someCondition" @change="onChange">

/* Data */
data(){
    return {
        someCondition: true
    }
}

/* Methods */
//file的change事件:
onChange(e){
    //想办法让someCondition经历 false - true 的转换
    this.someCondition = false;
    ...some code
    this.someCondition = true
}

现在,input[type=file] 已经是一个全新的了,即使你选择同一个文件,它也会响应 change 事件。

例子2

element-uitree 控件不是很完善,不提供刷新方法(也许是我没有发现)。如果你修改了某一个子项,然后想要重新加载整个树,就可以使用 v-if 来达到目的。用法跟上面是一样的。

End

在 uni-app 中使用自定义 tabBar,需要注意的一共有 3 点:app.json的配置、custom-tab-bar目录的建立、tab 页中getTabBar方法的调用。

首先根据官方文档,配置app.json。在 uni-app 项目中,app.json对应的是pages.json,每个页面的.json文件,需要写在pages.jsonpages-style段中:

{
    "tabBar": {
        "custom": true,
        "color": "#000000",
        "selectedColor": "#000000",
        "backgroundColor": "#000000",
        "list": [
            {
               "pagePath": "pages/index/index",
                "text": "首页"
            },
            {
                "pagePath": "pages/me/index",
                "text": "我的"
            }
        ]
    },
    "pages": [
        {
            "path": "pages/index/index",
            "style": {
                "navigationBarTitleText": "首页",
                "usingComponents": {}
            }
        },
        {
            "path": "pages/me/index",
           "style": {
                "navigationBarTitleText": "我的",
                "usingComponents": {}
            }
        }
    ],
    ......
}

然后需要在根目录下新建custom-tab-bar目录,在这个目录中手动新建小程序四件套,即index.wxmlindex.jsindex.wxssindex.json。这个文件夹会被原封不动地复制到编译好的项目中。接着就是编写 tabBar 的样式和逻辑,这和原生开发是一样的,这里不再赘述。

最后需要在 tab 页面中调用getTabBar方法。uni-app 的页面是.vue文件。在onShow事件中,添加如下代码:
onShow() {
this.$mp.page.getTabBar().setData({
selectdIndex: 1
});
}
注意,getTabBar方法并不是直接在this上,而是在this.$mp.page上。

还有一点需要注意的是,tabBar 会占用一部分页面高度,tab 页最下方的内容会被遮住,需要处理一下这个问题,比如给 tab 页增加一个与 tabBar 高度一样的padding-bottom

Vuex 方便了管理状态,但也使开发流程更加复杂。为了提高开发速度,“让你少按几次键”,Vuex 提供了诸如mapState方法,但其实帮助不大。如果你在 methods 中看到调用了一个陌生的方法,那十有八九是 Vuex 中的 Action 或者 Mutation。

要是你想使用 v-model 绑定 state 中的字段,享受双向绑定的便捷,那在享受之前,你要好好忍受一下折磨,因为要实现这个功能是很麻烦的:在定义了 state 后,还要定义对应的 mutation;导入到组件的 computed 时,不能使用 mapState,还要给导入的 state 设置 setter,在 setter 中 commit mutation。这是至关重要的一步,否则 v-model 不会正常工作,而是会警告你缺少 setter:
Computed property "xxxx" was assigned to but it has no setter
这实在是很麻烦,必须找到更快捷的方法,更愉快地使用 v-model。这里提供一个简单的方法,应该会提高一点点开发速度。

1、定义通用的 mutation

如果给每一个 state 都定义相应的 mutation,那代码就要写到天荒地老。不妨定义一个通用的 mutation,只需传入需要修改的 state 的字段名和新值:
updateState(state, payload){
state[payload.key] = payload.val;
}
这样就方便多了。至于一些需要特殊处理的 state,再单独给它们定义 mutation。

2、生成带 setter 的 computed

能用程序生成的,怎么能复制粘贴修改呢?computed 是一个对象,咱们就生成一个对象,再用...操作符合并到组件的 computed 中。

首先在你喜欢的地方新建一个 .js 文件。因为提交 mutation 需要用到 store 实例,store 实例又在 Vue 实例上,咱们就从 main.js 或任何你能想到的地方导出 Vue 实例,在刚才新建的 .js 文件中导入:
import vm from '@/main'
接着,定义一个数组,元素是 state 的字段名:
let stateKeys = ['name', 'age', 'sex'];
接着,循环这个数组,生成计算属性:
let _state = {};
stateKeys.map(key =>{
_state[key] = {
get(){
//如果你的state在模块中,记得在state后加上.模块名,下同
return vm.$store.state[key];
},
set(val){
vm.$store.commit('updateState', {
key,
val
});
}
}
})

export const state = _state;

之后,在组件中引入刚刚的 state:
import state from '刚刚的js路径和名称'
最后,在组件的 computed 属性中导入 state:
computed: {
...state
}
现在,就能在模板中正常使用 state 了:
<input v-model="name">

最后

一个人不喜欢刷碗不一定是个懒人,他可能只是不喜欢干类似的活。程序员一般都不喜欢干复制粘贴这样的重复性工作 :)

End

我是菜鸟,今天才发现这个问题。

Vue计算属性看似是一个函数,但实际上跟 data 更像。如果计算属性返回的是一个对象,并且在模板中读取了这个对象的属性,那就要保证开始时这个对象的属性一定要存在,下面是有问题的代码:

<header>{{ pageInfo.title }}</header>

...
computed: {
    pageInfo(){
        const mapPageInfo = {
            'index': {
                title: 'index page',
                subTitle: 'index subtitle'
            },
            'list': {
                title: 'list page',
                subTitle: 'list subtitle'
            }
        };

        return mapPageInfo[this.$route.path]
    }
}
....

上面的代码看起来没有问题,运行起来也是正常的,但实际上在控制台报错了:
cannot read property title of undefined...
原因就是 pageInfo 在刚开始的时候没有组件中读取的 title 属性。下面是正确的做法:
...
pageInfo(){
//保证属性一定是存在的
let pageInfo = {
title: '',
subTitle: ''
};
const mapPageInfo = {
...
}
....
return Object.assign(pageInfo, mapPageInfo[this.$route.path]);
}
...

End

wx.onAppRoute已经失效

最近试图使用wx.onAppRoute来设置一些全局的东西,比如给每个页面都设置onShareAppMessage方法,让每个页面都可以分享:
//app.js
onLaunch(){
wx.onAppRoute(()=>{
const pages = getCurrentPages();
const currPage = pages[pages.length - 1];

        if (currPage.onShareAppMessage !== undefined) return;

        currPage.onShareAppMessage = ()=>{
            return { title: 'Hi' }
        }
    })
}

这样的做法已经失效了,点击右上角的三个点,分享按钮还是灰色的。
难道是我使用的方法不对吗?如果有错误请告诉我~

还可以使用behaviors

幸运的是,现在Page也支持behaviors了,虽然还是要每个页面都引用定义的behaviors,但是总比每个页面都写上一段相同的代码好一点。

End

对象和数组

利用Object()方法来判断一个变量是否为对象:

如果Object方法的参数是一个对象,它总是返回该对象,即不用转换。这一点可以用来判断变量是都是对象。
var obj = {};
var isObject = obj === Object(obj);

利用Object.prototype.toString()方法判断变量类型

toString方法返回变量的类型字符串,可以用来判断变量类型。由于数组、字符串、函数、Date等对象(构造函数)都自定义了自己的toString方法,所以要使用Object.prototype.toString()
Object.prototype.toString.call(2); // "[object Number]"
Object.prototype.toString.call(''); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(Math); // "[object Math]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
这个方法比typeof更准确,例如:
typeof []; //'object'

利用Array.isArray()判断变量是否为数组

同样的,这个方法也可以弥补typeof的不足:
typeof []; // 'object'
Array.isArray([]); // true

关于数组的实例方法concat()

“如果数组成员包括对象,concat方法返回当前数组的一个浅拷贝。所谓“浅拷贝”,指的是新数组拷贝的是对象的引用。”
https://wangdoc.com/javascript/stdlib/array.html

确切的意思是,如果数组成员包括对象,concat方法返回的新数组中,对象成员是原数组对象成员的引用:
var arr = [1, 2, {a: 3}];
var arr1 = arr.concat(4); // [1, 2, {a: 3}, 4]
console.log(arr1[2]); // {a: 3}
arr1[2].a = 5;
console.log(arr[2]); // {a: 5}

会改变原数组的方法

pop push shift unshift reverse splice, sort

slice方法

1、不传参数时,会返回原数组的拷贝

[1, 2, 3].slice(); // 返回原数组的拷贝 [1, 2, 3]

同样的,对于包含对象成员的数组,返回的新数组中的对象仍然是原数组的浅拷贝,也就是引用。

2、可以将类数组对象转换成整整的数组

var arrLikeObj = { 0: 1, 1: 2, length: 2};
Array.prototype.slice.call(arrLikeObj); // [1, 2]

类数组对象,就是具有数字类型的键值,又具有length属性的对象。

利用splice方法插入新元素

splice方法的第二个参数是0时,表示只插入新元素,不删除任何元素:
var arr = [1, 2, 3];
arr.splice(0, 0, 0);
console.log(arr); // [0, 1, 2, 3]

众所周知,在 html 中,下面的代码:

<div style="height: 100px; padding: 20px">
    <div class="inner" style="height: 100%"></div>
</div>

.inner的高度是100px。如果给外面的 div 设置 box-sizing: border-box,那么.inner的高度将会变成100px - 20px - 20px,也就是60px

但是在微信小程序的 scroll-view 组件中却不同:



.inner的高度将会是100px + 20px + 20px = 140px。如果给外面的 div 设置 box-sizing: border-box,那么.inner的高度将会变成100px - 20px - 20px + 20px + 20px,也就是100px

也就是说,scroll-view 的高度设置成 100% 后,它会忽略父容器的 padding,始终等于父元素的总高度,设置box-sizing: border-box 也不能够让它表现正常。

所以,如果你想要设置 scroll-view 直接子元素的高度为 100%,那么尽可能使用绝对长度,不要使用 100%。

END