青岛小龟

青岛小龟 查看完整档案

青岛编辑青岛滨海学院  |  网络工程 编辑  |  填写所在公司/组织 www.aiyuqin.com 编辑
编辑

Hello.world!

个人动态

青岛小龟 关注了标签 · 2020-09-30

element-ui

Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库

A Vue.js 2.0 UI Toolkit for Web

https://element.eleme.cn/
https://github.com/ElemeFE/el...

关注 661

青岛小龟 收藏了文章 · 2020-09-28

解锁各种js数组骚操作,总有你想要的!

在开发中,数组的使用场景非常多,平日中也涉及到很多数组相关操作,对一些常见的操作方法进行总结和收藏,在开发中就能信手拈来,大大提高开发效率。

本文在gitthub做了收录 github.com/Michael-lzg…

随机排序

1、生成随机数

遍历数组,每次循环都随机一个在数组长度范围内的数,并交换本次循环的位置和随机数位置上的元素

function randomSort1(arr) {
  for (let i = 0, l = arr.length; i < l; i++) {
    let rc = parseInt(Math.random() * l)
    // 让当前循环的数组元素和随机出来的数组元素交换位置
    const empty = arr[i]
    arr[i] = arr[rc]
    arr[rc] = empty
  }
  return arr
}

var arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 下面两次的结果肯定是不一样的;
console.log(randomSort1(arr1))
console.log(randomSort1(arr1))

2、生成新数组

  1. 申明一个新的空数组,利用 while 循环,如果数组长度大于 0,就继续循环;
  2. 每次循环都随机一个在数组长度范围内的数,将随机数位置上的元素 push 到新数组里,
  3. 并利用 splice(对 splice 不太理解的同学可以看这里)截取出随机数位置上的元素,同时也修改了原始数组的长度;
function randomSort2(arr) {
  var mixedArr = []
  while (arr.length > 0) {
    let rc = parseInt(Math.random() * arr.length)
    mixedArr.push(arr[rc])
    arr.splice(rc, 1)
  }
  return mixedArr
}
// 例子
var arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]

console.log(randomSort2(arr1))

3、 arr.sort

  1. 如果 compareFunction(a, b)的返回值 小于 0 ,那么 a 会被排列到 b 之前;
  2. 如果 compareFunction(a, b)的返回值 等于 0 ,那么 a 和 b 的相对位置不变;
  3. 如果 compareFunction(a, b)的返回值 大于 0 ,那么 b 会被排列到 a 之前;
function randomSort3(arr) {
  arr.sort(function (a, b) {
    return Math.random() - 0.5
  })
  return arr
}
// 例子
var arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]

console.log(randomSort3(arr1))

数组对象排序

1、单个属性排序

function compare(property) {
  return function (a, b) {
    let value1 = a[property]
    let value2 = b[property]
    return value1 - value2
  }
}

let arr = [
  { name: 'zopp', age: 10 },
  { name: 'gpp', age: 18 },
  { name: 'yjj', age: 8 },
]

console.log(arr.sort(compare('age')))

2、多个属性排序

function by(name, minor) {
  return function(o, p) {
    let a, b
    if (o && p && typeof o === 'object' && typeof p === 'object') {
      a = o[name]
      b = p[name]
      if (a === b) {
        return typeof minor === 'function' ? minor(o, p) : 0
      }
      if (typeof a === typeof b) {
        return a < b ? -1 : 1
      }
      return typeof a < typeof b ? -1 : 1
    } else {
      thro('error')
    }
  }
},

数组扁平化

1、调用 ES6 中的 flat 方法

ary = arr.flat(Infinity)

console.log([1, [2, 3, [4, 5, [6, 7]]]].flat(Infinity))

2、普通递归

let result = []
let flatten = function (arr) {
  for (let i = 0; i < arr.length; i++) {
    let item = arr[i]
    if (Array.isArray(arr[i])) {
      flatten(item)
    } else {
      result.push(item)
    }
  }
  return result
}

let arr = [1, 2, [3, 4], [5, [6, 7]]]
console.log(flatten(arr))

3、利用 reduce 函数迭代

function flatten(arr) {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur)
  }, [])
}

let arr = [1, 2, [3, 4], [5, [6, 7]]]
console.log(flatten(arr))

4、扩展运算符

function flatten(arr) {
  while (arr.some((item) => Array.isArray(item))) {
    arr = [].concat(...arr)
  }
  return arr
}

let arr = [1, 2, [3, 4], [5, [6, 7]]]
console.log(flatten(arr))

数组去重

1、利用数组的 indexOf 下标属性来查询

function unique(arr) {
  var newArr = []
  for (var i = 0; i < arr.length; i++) {
    if (newArr.indexOf(arr[i]) === -1) {
      newArr.push(arr[i])
    }
  }
  return newArr
}
console.log(unique([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))

2、先将原数组排序,在与相邻的进行比较,如果不同则存入新数组。

function unique(arr) {
  var formArr = arr.sort()
  var newArr = [formArr[0]]
  for (let i = 1; i < formArr.length; i++) {
    if (formArr[i] !== formArr[i - 1]) {
      newArr.push(formArr[i])
    }
  }
  return newArr
}
console.log(unique([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))

3、利用对象属性存在的特性,如果没有该属性则存入新数组。

function unique(arr) {
  var obj = {}
  var newArr = []
  for (let i = 0; i < arr.length; i++) {
    if (!obj[arr[i]]) {
      obj[arr[i]] = 1
      newArr.push(arr[i])
    }
  }
  return newArr
}
console.log(unique([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))

4、利用数组原型对象上的 includes 方法。

function unique(arr) {
  var newArr = []
  for (var i = 0; i < arr.length; i++) {
    if (!newArr.includes(arr[i])) {
      newArr.push(arr[i])
    }
  }
  return newArr
}
console.log(unique([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))

5、利用数组原型对象上的 filter 和 includes 方法。

function unique(arr) {
  var newArr = []
  newArr = arr.filter(function (item) {
    return newArr.includes(item) ? '' : newArr.push(item)
  })
  return newArr
}
console.log(unique([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))

6、利用 ES6 的 set 方法。

function unique(arr) {
  return Array.from(new Set(arr)) // 利用Array.from将Set结构转换成数组
}
console.log(unique([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))

根据属性去重

方法一

function unique(arr) {
  const res = new Map()
  return arr.filter((item) => !res.has(item.productName) && res.set(item.productName, 1))
}

方法二

function unique(arr) {
  let result = {}
  let obj = {}
  for (var i = 0; i < arr.length; i++) {
    if (!obj[arr[i].key]) {
      result.push(arr[i])
      obj[arr[i].key] = true
    }
  }
}

交集/并集/差集

1、includes 方法结合 filter 方法

let a = [1, 2, 3]
let b = [2, 4, 5]

// 并集
let union = a.concat(b.filter((v) => !a.includes(v)))
// [1,2,3,4,5]

// 交集
let intersection = a.filter((v) => b.includes(v))
// [2]

// 差集
let difference = a.concat(b).filter((v) => !a.includes(v) || !b.includes(v))
// [1,3,4,5]

2、ES6 的 Set 数据结构

let a = new Set([1, 2, 3])
let b = new Set([2, 4, 5])

// 并集
let union = new Set([...a, ...b])
// Set {1, 2, 3, 4,5}

// 交集
let intersect = new Set([...a].filter((x) => b.has(x)))
// set {2}

// 差集
let difference = new Set([...a].filter((x) => !b.has(x)))
// Set {1, 3, 4, 5}

数组求和

1、万能的 for 循环

function sum(arr) {
  var s = 0
  for (var i = arr.length - 1; i >= 0; i--) {
    s += arr[i]
  }
  return s
}

sum([1, 2, 3, 4, 5]) // 15

2、递归方法

function sum(arr) {
  var len = arr.length
  if (len == 0) {
    return 0
  } else if (len == 1) {
    return arr[0]
  } else {
    return arr[0] + sum(arr.slice(1))
  }
}

sum([1, 2, 3, 4, 5]) // 15

3、ES6 的 reduce 方法

function sum(arr) {
  return arr.reduce(function (prev, curr) {
    return prev + curr
  }, 0)
}

sum([1, 2, 3, 4, 5]) // 15

类数组转化

1、Array 的 slice 方法

let arr = Array.prototype.slice.call(arguments)

2、ES6 的 Array.from()

let arr = Array.from(arguments)

3、扩展运算符...

let arr = [...arguments]

数组上下移动

function swapItems(arr, index1, index2) {
  arr[index1] = arr.splice(index2, 1, arr[index1])[0]
  return arr
}

function up(arr, index) {
  if (index === 0) {
    return
  }
  this.swapItems(arr, index, index - 1)
}

function down(arr, index) {
  if (index === this.list.length - 1) {
    return
  }
  this.swapItems(arr, index, index + 1)
}

数组转化为树形结构

将如下数据转化为树状结构

let arr = [
  {
    id: 1,
    name: '1',
    pid: 0,
  },
  {
    id: 2,
    name: '1-1',
    pid: 1,
  },
  {
    id: 3,
    name: '1-1-1',
    pid: 2,
  },
  {
    id: 4,
    name: '1-2',
    pid: 1,
  },
  {
    id: 5,
    name: '1-2-2',
    pid: 4,
  },
  {
    id: 6,
    name: '1-1-1-1',
    pid: 3,
  },
  {
    id: 7,
    name: '2',
  },
]

实现方法

function toTree(data, parentId = 0) {
  var itemArr = []
  for (var i = 0; i < data.length; i++) {
    var node = data[i]
    if (node.pid === parentId) {
      var newNode = {
        ...node,
        name: node.name,
        id: node.id,
        children: toTree(data, node.id),
      }
      itemArr.push(newNode)
    }
  }
  return itemArr
}

console.log(toTree(arr))

推荐文章

总结18个webpack插件,总会有你想要的!
搭建一个 vue-cli4+webpack 移动端框架(开箱即用)
从零构建到优化一个类似vue-cli的脚手架
封装一个toast和dialog组件并发布到npm
从零开始构建一个webpack项目
总结几个webpack打包优化的方法
总结vue知识体系之高级应用篇
总结vue知识体系之实用技巧
总结vue知识体系之基础入门篇
总结移动端H5开发常用技巧(干货满满哦!)

查看原文

青岛小龟 收藏了文章 · 2020-08-03

提升90%加载速度——vuecli下的首屏性能优化

前言

之前用vuecli做了个博客,是一个单页面项目,大概有十个路由
直接npm run build打包出来,有一个1M的巨大js文件

clipboard.png

先挂载到服务器上试试
好家伙 这加载时间 仿佛过了半个世纪

clipboard.png

首屏页面整整加载了9s 光加载那个大文件就花了8s
这必须得做个优化了,没有用户能忍受9s的白屏而不关闭页面的

过程中,我还顺便把项目从vuecli 2.x迁移到了vuecli 3,所以接下来还会介绍一些它们在优化上的异同

分析

vuecli 2.x自带了分析工具
只要运行npm run build --report

如果是vuecli 3的话,先安装插件

cnpm intall webpack-bundle-analyzer –save-dev

然后在vue.config.js中对webpack进行配置

chainWebpack: (config) => {
    /* 添加分析工具*/
    if (process.env.NODE_ENV === 'production') {
        if (process.env.npm_config_report) {
            config
                .plugin('webpack-bundle-analyzer')
                .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
                .end();
            config.plugins.delete('prefetch')
        }
    } }

再运行npm run build --report

会在浏览器打开一个项目打包的情况图,便于直观地比较各个bundle文件的大小

clipboard.png

可以看到 项目中所有的依赖,所有的路由,都被打包进了同一个文件中

另外,在浏览器中,也可以通过converge来查看代码的使用状况

clipboard.png
红色的是下载了但未使用的部分

路由懒加载

当打包构建应用时,JavaScript包会变得非常大,影响页面加载。
如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

在一开始就下载完所有路由对应的组件文件,这明显是不合适的,这就像下载一个app了,所以我们就需要使用路由懒加载

clipboard.png

router.js文件中,原来的静态引用方式

import ShowBlogs from '@/components/ShowBlogs'

routes:[ path: 'Blogs', name: 'ShowBlogs', component: ShowBlogs ]

改为

 routes:[ path: 'Blogs',name: 'ShowBlogs',component: () => import('./components/ShowBlogs.vue')

以函数的形式动态引入,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会下载路由组件

clipboard.png

首屏需要加载的文件变成了橙色的部分,被小弟们分流出去了300k

如果是在vuecli 3中,我们还需要多做一步工作
因为vuecli 3默认开启prefetch(预先加载模块),提前获取用户未来可能会访问的内容
在首屏会把这十几个路由文件,都一口气下载了
所以我们要关闭这个功能,在vue.config.js中设置
参考官网的做法:

clipboard.png

设置完毕后,首屏就只会加载当前页面路由的组件了

element-ui按需加载

clipboard.png

首屏需要加载的依赖包,其中element-ui整整占了568k
原本的引进方式引进了整个包:

import ElementUI from 'element-ui'
Vue.use(ElementUI)

但实际上我用到的组件只有按钮,分页,表格,输入与警告
所以我们要按需引用

import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)
Vue.prototype.$alert = MessageBox.alert

注意MessageBox注册方法的区别,并且我们虽然用到了alert,但并不需要引入Alert组件

.babelrc文件中添加(vue-cli 3要先安装babel-plugin-component):

plugins: [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]

clipboard.png
element-ui小了很多,不过看到那个显眼的table.js后想到,table组件只有后台管理页面用到了,不需要全局注册,所以我们删除main.jsTableTablColumn的引用,并在后台组件中局部注册

import { Table, TableColumn } from "element-ui";

components: {
    "el-table": Table,
    "el-table-column": TableColumn },

clipboard.png
table就被拆分到了路由文件中

组件重复打包

可以看到上图,有两个路由文件都引用了codemirror.js造成重复下载
我们可以在webpackconfig文件中,修改CommonsChunkPlugin的配置

minChunks: 3

把3改为2,就会把使用2次及以上的包抽离出来,放进公共依赖文件,不过由于首页也有复用的组件,所以首页也会下载这个公共依赖文件

clipboard.png

首页下载了黄色和灰色部分
拆了半天,又回到原点

当然,我们可以继续折腾CommonsChunkPlugin的配置来解决这个问题
但在新版webpack中,CommonsChunkPlugin被自由度更高,更高级的SplitChunksPlugin代替

这也是为什么我要把项目迁移到vuecli 3(使用webpack4
默认就做了优化,首页只会下载灰色的部分(235K

gzip

拆完包之后,我们再用gzip做一下压缩
安装compression-webpack-plugin

cnmp i compression-webpack-plugin -D

vue.congig.js中引入并修改webpack配置

const CompressionPlugin = require('compression-webpack-plugin')

configureWebpack: (config) => {
        if (process.env.NODE_ENV === 'production') {
            // 为生产环境修改配置...
            config.mode = 'production'
            return {
                plugins: [new CompressionPlugin({
                    test: /\.js$|\.html$|\.css/, //匹配文件名
                    threshold: 10240, //对超过10k的数据进行压缩
                    deleteOriginalAssets: false //是否删除原文件
                })]
            }
        }

clipboard.png
可以看到200k以上的文件被压缩到了100k以内

在服务器我们也要做相应的配置
如果发送请求的浏览器支持gzip,就发送给它gzip格式的文件
我的服务器是用express框架搭建的
只要安装一下compression就能使用

const compression = require('compression')
app.use(compression())

注意,后面这一句,要放在所有其他中间件注册之前

最终效果

clipboard.png
首屏加载资源198k,加载时间1s,相比原来速度提升了90%

后记:css是否要拆分

vuecli 3vuecli2.x还有一个区别是
vuecli 3会默认开启一个css分离插件 ExtractTextPlugin
每一个模块的css文件都会分离出来,整整13个css文件,而我们的首页就请求了4个,花费了不少的资源请求时间
我们可以在vue.config.js中关闭它

    css: {
    // 是否使用css分离插件 ExtractTextPlugin
    extract: false,
    // 开启 CSS source maps?
    sourceMap: false,
    // css预设器配置项
    loaderOptions: {},
    // 启用 CSS modules for all css / pre-processor files.
    modules: false
},

clipboard.png

clipboard.png

打包出来的文件中,直接就没有了css文件夹
取而代之的是整合起来的一个js文件,负责在一开始就注入所有的样式
首屏加载文件数减少,但体积变大,最终测下来速度没有太大差异
所以,是否要css拆分就见仁见智,具体项目具体分析吧

总结

性能优化是一个非常令人愉悦的过程,同时也是个深坑,有着太多东西,本篇文章开了个头,希望能对大家有所帮助

参考文章

Vue打包优化之code spliting
https://juejin.im/post/5ac815...
Vue 性能优化:如何实现延迟加载和代码拆分?
https://www.infoq.cn/article/...*zN
Webpack 打包优化之体积篇
https://www.jeffjade.com/2017...
记一次vue+element+echarts项目的优化
https://juejin.im/post/5b0033...

查看原文

青岛小龟 赞了文章 · 2020-07-16

一文读懂Base64编码

一、为什么要使用 base64

我们知道一个字节可表示的范围是 0 ~ 255(十六进制:0x00 ~ 0xFF), 其中 ASCII 值的范围为 0 ~ 127(十六进制:0x00 ~ 0x7F);而超过 ASCII 范围的 128~255(十六进制:0x80 ~ 0xFF)之间的值是不可见字符。

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语,而其扩展版本延伸美国标准信息交换码则可以部分支持其他西欧语言,并等同于国际标准 ISO/IEC 646。

在 ASCII 码中 0 - 31 和 127 是控制字符,共 33 个。以下是其中一部分控制字符:

ascii-controller-table.png

其余 95 个,即 32 - 126 是可打印字符,包括数字、大小写字母、常用符号等。

ascii-printable-table.png

当不可见字符在网络上传输时,比如说从 A 计算机传到 B 计算机,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。为了解决这个问题,我们可以先对数据进行编码,比如 base64 编码,变成可见字符,也就是 ASCII 码可表示的可见字符,从而确保数据可靠传输。Base64 的内容是有 0 ~ 9,a ~ z,A ~ Z,+,/ 组成,正好 64 个字符,这些字符是在 ASCII 可表示的范围内,属于 95 个可见字符的一部分。

二、什么是 base64

Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法。由于 2⁶ = 64 ,所以每 6 个比特为一个单元,对应某个可打印字符。3 个字节有 24 个比特,对应于 4 个 base64 单元,即 3 个字节可由 4 个可打印字符来表示。相应的转换过程如下图所示:

base64-convert.png

Base64 常用于在处理文本数据的场合,表示、传输、存储一些二进制数据,包括 MIME 的电子邮件及 XML 的一些复杂数据。在 MIME 格式的电子邮件中,base64 可以用来将二进制的字节序列数据编码成 ASCII 字符序列构成的文本。使用时,在传输编码方式中指定 base64。使用的字符包括大小写拉丁字母各 26 个、数字 10 个、加号 + 和斜杠 /,共 64 个字符,等号 = 用来作为后缀用途。Base64 相应的索引表如下:

base64-encode-table.png

了解完上述的知识,我们以编码 Man 为例,来直观的感受一下编码过程。Man 由 M、a 和 n 3 个字符组成,它们对应的 ASCII 码为 77、97 和 110。

char-m-ascii.jpg

接着我们以每 6 个比特为一个单元,进行 base64 编码操作,具体如下图所示:

base64-encode-demo.jpg

由图可知,Man (3字节)编码的结果为 TWFu(4字节),很明显经过 base64 编码后体积会增加 1/3。Man 这个字符串的长度刚好是 3,我们可以用 4 个 base64 单元来表示。但如果待编码的字符串长度不是 3 的整数倍时,应该如何处理呢?

如果要编码的字节数不能被 3 整除,最后会多出 1 个或 2 个字节,那么可以使用下面的方法进行处理:先使用 0 字节值在末尾补足,使其能够被 3 整除,然后再进行 base64 的编码。

以编码字符 A 为例,其所占的字节数为 1,不能被 3 整除,需要补 2 个字节,具体如下图所示:

base64-encode-a.jpg

由上图可知,字符 A 经过 base64 编码后的结果是 QQ==,该结果后面的两个 = 代表补足的字节数。而最后个 1 个 base64 字节块有 4 位是 0 值。

接着我们来看另一个示例,假设需编码的字符串为 BC,其所占字节数为 2,不能被 3 整除,需要补 1 个字节,具体如下图所示:

base64-encode-bc.jpg

由上图可知,字符串 BC 经过 base64 编码后的结果是 QkM=,该结果后面的 1 个 = 代表补足的字节数。而最后个 1 个 base64 字节块有 2 位是 0 值。

三、base64 编码的应用

在 HTML 中嵌入 base64 编码的图片

在编写 HTML 网页时,对于一些简单图片,通常会选择将图片内容直接内嵌在网页中,从而减少不必要的网络请求,但是图片数据是二进制数据,该怎么嵌入呢?绝大多数现代浏览器都支持一种名为 Data URLs 的特性,允许使用 base64 对图片或其他文件的二进制数据进行编码,将其作为文本字符串嵌入网页中。

<img alt="logo" data-original="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...">

但需要注意的是:如果图片较大,图片的色彩层次比较丰富,则不适合使用这种方式,因为该图片经过 base64 编码后的字符串非常大,会明显增大 HTML 页面的大小,从而影响加载速度。除此之外,利用 HTML FileReader API,我们也可以方便的实现图片本地预览功能,具体代码如下:

<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>
<script>
  const loadFile = function(event) {
    const reader = new FileReader();
    reader.onload = function(){
      const output = document.querySelector('output');
      output.src = reader.result;
    };
    reader.readAsDataURL(event.target.files[0]);
  };
</script>

在完成本地图片预览之后,可以直接把图片对应的 Data URLs 数据提交到服务器。针对这种情形,服务端需要做一些相关处理,才能正常保存上传的图片,这里以 Express 为例,具体处理代码如下:

const app = require('express')();

app.post('/upload', function(req, res){
    let imgData = req.body.imgData; // 获取POST请求中的base64图片数据
    let base64Data = imgData.replace(/^data:image\/\w+;base64,/, "");
    let dataBuffer = Buffer.from(base64Data, 'base64');
    fs.writeFile("image.png", dataBuffer, function(err) {
        if(err){
          res.send(err);
        }else{
          res.send("图片上传成功!");
        }
    });
});

MIME(多用途互联网邮件扩展)

在 MIME 协议之前,邮件的编码曾经有过 UUENCODE 等编码方式 ,但是由于 MIME 协议算法简单,并且易于扩展,现在已经成为邮件编码方式的主流,不仅是用来传输 8 位的字符,也可以用来传送二进制的文件,如邮件附件中的图像、音频等信息,而且扩展了很多基于 MIME 的应用。

四、如何进行 base64 编码和解码

在 JavaScript 中,有两个函数被分别用来处理解码和编码 base64 字符串:

  • btoa():该函数能够基于二进制数据 “字符串” 创建一个 base64 编码的 ASCII 字符串。
  • atob(): 该函数能够解码通过 base64 编码的字符串数据。

btoa 使用示例

const name = 'Semlinker';
const encodedName = btoa(name);
console.log(encodedName); // U2VtbGlua2Vy

atob 使用示例

const encodedName = 'U2VtbGlua2Vy';
const name = atob(encodedName);
console.log(name); // Semlinker

对于 atob 和 btoa 这两个方法来说,其中的 a 代表 ASCII,而 b 代表 Blob,即二进制。因此 atob 表示 ASCII 到二进制,对应的是解码操作。而 btoa 表示二进制到 ASCII,对应的是编码操作。在了解方法中 a 和 b 分别代表的意义之后,在以后的工作中,我们就不会用错了。

五、总结

Base64 是一种数据编码方式,目的是为了保障数据的安全传输。但标准的 base64 编码无需额外的信息,即可以进行解码,是完全可逆的。因此在涉及传输私密数据时,并不能直接使用 base64 编码,而是要使用专门的对称或非对称加密算法。

六、参考资源

本人的全栈修仙之路订阅号,会定期分享 Angular、TypeScript、Node.js/Java 、Spring 相关文章,欢迎感兴趣的小伙伴订阅哈!

full-stack-logo

查看原文

赞 14 收藏 6 评论 0

青岛小龟 关注了用户 · 2020-07-16

lzg9527 @michael_5c03399eed011

专注web前端开发,熟悉html5,css3,javascript,vue,react

关注 7590

青岛小龟 关注了专栏 · 2020-07-16

全栈修仙之路

聚焦全栈,专注分享 Angular、TypeScript、Node.js/Java 、Spring 技术栈等全栈干货。 欢迎小伙伴们关注公众号全栈修仙之路,一起升级打怪。

关注 7075

青岛小龟 收藏了文章 · 2020-06-08

你不知道的 Blob

一、Blob 是什么

Blob(Binary Large Object)表示二进制类型的大对象。在数据库管理系统中,将二进制数据存储为一个单一个体的集合。Blob 通常是影像、声音或多媒体文件。在 JavaScript 中 Blob 类型的对象表示不可变的类似文件对象的原始数据。 为了更直观的感受 Blob 对象,我们先来使用 Blob 构造函数,创建一个 myBlob 对象,具体如下图所示:

plain-type-blob.jpg

如你所见,myBlob 对象含有两个属性:size 和 type。其中 size 属性用于表示数据的大小(以字节为单位),type 是 MIME 类型的字符串。Blob 表示的不一定是 JavaScript 原生格式的数据。比如 File 接口基于 Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。

创建了一个 “重学TypeScript” 的微信群,想加群的小伙伴,加我微信 "semlinker",备注重学TS。目前已出 TS 专题 39 篇。

二、Blob API 简介

Blob 由一个可选的字符串 type(通常是 MIME 类型)和 blobParts 组成:

blob-structure.jpg

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型,是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。

常见的 MIME 类型有:超文本标记语言文本 .html text/html、PNG图像 .png image/png、普通文本 .txt text/plain 等。

2.1 构造函数

Blob 构造函数的语法为:

var aBlob = new Blob(blobParts, options);

相关的参数说明如下:

  • blobParts:它是一个由 ArrayBuffer,ArrayBufferView,Blob,DOMString 等对象构成的数组。DOMStrings 会被编码为 UTF-8。
  • options:一个可选的对象,包含以下两个属性:

    • type —— 默认值为 "",它代表了将会被放入到 blob 中的数组内容的 MIME 类型。
    • endings —— 默认值为 "transparent",用于指定包含行结束符 \n 的字符串如何被写入。 它是以下两个值中的一个: "native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持 blob 中保存的结束符不变。

示例一:从字符串创建 Blob

let myBlobParts = ['<html><h2>Hello Semlinker</h2></html>']; // an array consisting of a single DOMString
let myBlob = new Blob(myBlobParts, {type : 'text/html', endings: "transparent"}); // the blob

console.log(myBlob.size + " bytes size");
// Output: 37 bytes size
console.log(myBlob.type + " is the type");
// Output: text/html is the type

示例二:从类型化数组和字符串创建 Blob

let hello = new Uint8Array([72, 101, 108, 108, 111]); // 二进制格式的 "hello"
let blob = new Blob([hello, ' ', 'semlinker'], {type: 'text/plain'});

介绍完 Blob 构造函数,接下来我们来分别介绍 Blob 类的属性和方法:

plain-type-blob-proto.jpg

2.2 属性

前面我们已经知道 Blob 对象包含两个属性:

  • size(只读):表示 Blob 对象中所包含数据的大小(以字节为单位)。
  • type(只读):一个字符串,表明该 Blob 对象所包含数据的 MIME 类型。如果类型未知,则该值为空字符串。

2.3 方法

  • slice([start[, end[, contentType]]]):返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据。
  • stream():返回一个能读取 blob 内容的 ReadableStream
  • text():返回一个 Promise 对象且包含 blob 所有内容的 UTF-8 格式的 USVString
  • arrayBuffer():返回一个 Promise 对象且包含 blob 所有内容的二进制格式的 ArrayBuffer

这里我们需要注意的是,Blob 对象是不可改变的。我们不能直接在一个 Blob 中更改数据,但是我们可以对一个 Blob 进行分割,从其中创建新的 Blob 对象,将它们混合到一个新的 Blob 中。这种行为类似于 JavaScript 字符串:我们无法更改字符串中的字符,但可以创建新的更正后的字符串。

三、Blob 使用场景

3.1 分片上传

File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的上下文中。所以针对大文件传输的场景,我们可以使用 slice 方法对大文件进行切割,然后分片进行上传,具体示例如下:

const file = new File(["a".repeat(1000000)], "test.txt");

const chunkSize = 40000;
const url = "https://httpbin.org/post";

async function chunkedUpload() {
  for (let start = 0; start < file.size; start += chunkSize) {
      const chunk = file.slice(start, start + chunkSize + 1);
      const fd = new FormData();
      fd.append("data", chunk);

      await fetch(url, { method: "post", body: fd }).then((res) =>
        res.text()
      );
  }
}

3.2 从互联网下载数据

我们可以使用以下方法从互联网上下载数据并将数据存储到 Blob 对象中,比如:

const downloadBlob = (url, callback) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'blob'
    xhr.onload = () => {
    callback(xhr.response)
    }
    xhr.send(null)
}

当然除了使用 XMLHttpRequest API 之外,我们也可以使用 fetch API 来实现以流的方式获取二进制数据。这里我们来看一下如何使用 fetch API 获取线上图片并本地显示,具体实现如下:

const myImage = document.querySelector('img');
const myRequest = new Request('flowers.jpg');

fetch(myRequest)
  .then(function(response) {
    return response.blob();
  })
 .then(function(myBlob) {
   let objectURL = URL.createObjectURL(myBlob);
   myImage.src = objectURL;
});

当 fetch 请求成功的时候,我们调用 response 对象的 blob() 方法,从 response 对象中读取一个 Blob 对象,然后使用 createObjectURL() 方法创建一个 objectURL,然后把它赋值给 img 元素的 src 属性从而显示这张图片。

3.3 Blob 用作 URL

Blob 可以很容易的作为 <a><img> 或其他标签的 URL,多亏了 type 属性,我们也可以上传/下载 Blob 对象。下面我们将举一个 Blob 文件下载的示例,不过在看具体示例前我们得简单介绍一下 Blob URL。

1.Blob URL/Object URL

Blob URL/Object URL 是一种伪协议,允许 Blob 和 File 对象用作图像,下载二进制数据链接等的 URL 源。在浏览器中,我们使用 URL.createObjectURL 方法来创建 Blob URL,该方法接收一个 Blob 对象,并为其创建一个唯一的 URL,其形式为 blob:<origin>/<uuid>,对应的示例如下:

blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641

浏览器内部为每个通过 URL.createObjectURL 生成的 URL 存储了一个 URL → Blob 映射。因此,此类 URL 较短,但可以访问 Blob。生成的 URL 仅在当前文档打开的状态下才有效。它允许引用 <img><a> 中的 Blob,但如果你访问的 Blob URL 不再存在,则会从浏览器中收到 404 错误。

上述的 Blob URL 看似很不错,但实际上它也有副作用。虽然存储了 URL → Blob 的映射,但 Blob 本身仍驻留在内存中,浏览器无法释放它。映射在文档卸载时自动清除,因此 Blob 对象随后被释放。但是,如果应用程序寿命很长,那不会很快发生。因此,如果我们创建一个 Blob URL,即使不再需要该 Blob,它也会存在内存中。

针对这个问题,我们可以调用 URL.revokeObjectURL(url) 方法,从内部映射中删除引用,从而允许删除 Blob(如果没有其他引用),并释放内存。接下来,我们来看一下 Blob 文件下载的具体示例。

2.Blob 文件下载示例

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Blob 文件下载示例</title>
  </head>

  <body>
    <button id="downloadBtn">文件下载</button>
    <script data-original="index.js"></script>
  </body>
</html>

index.js

const download = (fileName, blob) => {
  const link = document.createElement("a");
  link.href = URL.createObjectURL(blob);
  link.download = fileName;
  link.click();
  link.remove();
  URL.revokeObjectURL(link.href);
};

const downloadBtn = document.querySelector("#downloadBtn");
downloadBtn.addEventListener("click", (event) => {
  const fileName = "blob.txt";
  const myBlob = new Blob(["一文彻底掌握 Blob Web API"], { type: "text/plain" });
  download(fileName, myBlob);
});

在示例中,我们通过调用 Blob 的构造函数来创建类型为 "text/plain" 的 Blob 对象,然后通过动态创建 a 标签来实现文件的下载。

3.4 Blob 转换为 Base64

URL.createObjectURL 的一个替代方法是,将 Blob 转换为 base64 编码的字符串。Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法,它常用于在处理文本数据的场合,表示、传输、存储一些二进制数据,包括 MIME 的电子邮件及 XML 的一些复杂数据。

在 MIME 格式的电子邮件中,base64 可以用来将二进制的字节序列数据编码成 ASCII 字符序列构成的文本。使用时,在传输编码方式中指定 base64。使用的字符包括大小写拉丁字母各 26 个、数字 10 个、加号 + 和斜杠 /,共 64 个字符,等号 = 用来作为后缀用途。

下面我们来介绍如何在 HTML 中嵌入 base64 编码的图片。在编写 HTML 网页时,对于一些简单图片,通常会选择将图片内容直接内嵌在网页中,从而减少不必要的网络请求,但是图片数据是二进制数据,该怎么嵌入呢?绝大多数现代浏览器都支持一种名为 Data URLs 的特性,允许使用 base64 对图片或其他文件的二进制数据进行编码,将其作为文本字符串嵌入网页中。

Data URLs 由四个部分组成:前缀(data:)、指示数据类型的 MIME 类型、如果非文本则为可选的 base64 标记、数据本身:

data:[<mediatype>][;base64],<data>

mediatype 是个 MIME 类型的字符串,例如 "image/jpeg" 表示 JPEG 图像文件。如果被省略,则默认值为 text/plain;charset=US-ASCII。如果数据是文本类型,你可以直接将文本嵌入(根据文档类型,使用合适的实体字符或转义字符)。如果是二进制数据,你可以将数据进行 base64 编码之后再进行嵌入。比如嵌入一张图片:

<img alt="logo" data-original="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...">

但需要注意的是:如果图片较大,图片的色彩层次比较丰富,则不适合使用这种方式,因为该图片经过 base64 编码后的字符串非常大,会明显增大 HTML 页面的大小,从而影响加载速度。 除此之外,利用 FileReader API,我们也可以方便的实现图片本地预览功能,具体代码如下:

<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>

<script>
  const loadFile = function(event) {
    const reader = new FileReader();
    reader.onload = function(){
      const output = document.querySelector('output');
      output.src = reader.result;
    };
    reader.readAsDataURL(event.target.files[0]);
  };
</script>

在以上示例中,我们为 file 类型输入框绑定 onchange 事件处理函数 loadFile,在该函数中,我们创建了一个 FileReader 对象并为该对象绑定 onload 相应的事件处理函数,然后调用 FileReader 对象的 readAsDataURL() 方法,把本地图片对应的 File 对象转换为 Data URL。

在完成本地图片预览之后,我们可以直接把图片对应的 Data URLs 数据提交到服务器。针对这种情形,服务端需要做一些相关处理,才能正常保存上传的图片,这里以 Express 为例,具体处理代码如下:

const app = require('express')();

app.post('/upload', function(req, res){
    let imgData = req.body.imgData; // 获取POST请求中的base64图片数据
    let base64Data = imgData.replace(/^data:image\/\w+;base64,/, "");
    let dataBuffer = Buffer.from(base64Data, 'base64');
    fs.writeFile("image.png", dataBuffer, function(err) {
        if(err){
          res.send(err);
        }else{
          res.send("图片上传成功!");
        }
    });
});

对于 FileReader 对象来说,除了支持把 Blob/File 对象转换为 Data URL 之外,它还提供了 readAsArrayBuffer()readAsText() 方法,用于把 Blob/File 对象转换为其它的数据格式。这里我们来看个 readAsArrayBuffer() 的使用示例:

// 从 blob 获取 arrayBuffer
let fileReader = new FileReader();

fileReader.onload = function(event) {
  let arrayBuffer = fileReader.result;
};
fileReader.readAsArrayBuffer(blob);

3.5 图片压缩

在一些场合中,我们希望在上传本地图片时,先对图片进行一定的压缩,然后再提交到服务器,从而减少传输的数据量。在前端要实现图片压缩,我们可以利用 Canvas 对象提供的 toDataURL() 方法,该方法接收 typeencoderOptions 两个可选参数。

其中 type 表示图片格式,默认为 image/png。而 encoderOptions 用于表示图片的质量,在指定图片格式为 image/jpegimage/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92,其他参数会被忽略。

下面我们来看一下具体如何实现图片压缩:

// compress.js
const MAX_WIDTH = 800; // 图片最大宽度

function compress(base64, quality, mimeType) {
  let canvas = document.createElement("canvas");
  let img = document.createElement("img");
  img.crossOrigin = "anonymous";
  return new Promise((resolve, reject) => {
    img.src = base64;
    img.onload = () => {
      let targetWidth, targetHeight;
      if (img.width > MAX_WIDTH) {
        targetWidth = MAX_WIDTH;
        targetHeight = (img.height * MAX_WIDTH) / img.width;
      } else {
        targetWidth = img.width;
        targetHeight = img.height;
      }
      canvas.width = targetWidth;
      canvas.height = targetHeight;
      let ctx = canvas.getContext("2d");
      ctx.clearRect(0, 0, targetWidth, targetHeight); // 清除画布
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      let imageData = canvas.toDataURL(mimeType, quality / 100);
      resolve(imageData);
    };
  });
}

对于返回的 Data URL 格式的图片数据,为了进一步减少传输的数据量,我们可以把它转换为 Blob 对象:

function dataUrlToBlob(base64, mimeType) {
  let bytes = window.atob(base64.split(",")[1]);
  let ab = new ArrayBuffer(bytes.length);
  let ia = new Uint8Array(ab);
  for (let i = 0; i < bytes.length; i++) {
    ia[i] = bytes.charCodeAt(i);
  }
  return new Blob([ab], { type: mimeType });
}

在转换完成后,我们就可以压缩后的图片对应的 Blob 对象封装在 FormData 对象中,然后再通过 AJAX 提交到服务器上:

function uploadFile(url, blob) {
  let formData = new FormData();
  let request = new XMLHttpRequest();
  formData.append("image", blob);
  request.open("POST", url, true);
  request.send(formData);
}

其实 Canvas 对象除了提供 toDataURL() 方法之外,它还提供了一个 toBlob() 方法,该方法的语法如下:

canvas.toBlob(callback, mimeType, qualityArgument)

toDataURL() 方法相比,toBlob() 方法是异步的,因此多了个 callback 参数,这个 callback 回调方法默认的第一个参数就是转换好的 blob文件信息。

介绍完上述的内容,我们来看一下本地图片压缩完整的示例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>本地图片压缩</title>
  </head>
  <body>
    <input type="file" accept="image/*" onchange="loadFile(event)" />
    <script data-original="./compress.js"></script>
    <script>
      const loadFile = function (event) {
        const reader = new FileReader();
        reader.onload = async function () {
          let compressedDataURL = await compress(
            reader.result,
            90,
            "image/jpeg"
          );
          let compressedImageBlob = dataUrlToBlob(compressedDataURL);
          uploadFile("https://httpbin.org/post", compressedImageBlob);
        };
        reader.readAsDataURL(event.target.files[0]);
      };
    </script>
  </body>
</html>

3.6 生成 PDF 文档

PDF(便携式文件格式,Portable Document Format)是由 Adobe Systems 在 1993 年用于文件交换所发展出的文件格式。在浏览器端,利用一些现成的开源库,比如 jsPDF,我们也可以方便地生成 PDF 文档。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>客户端生成 PDF 示例</title>
  </head>
  <body>
    <h3>客户端生成 PDF 示例</h3>
    <script data-original="https://unpkg.com/jspdf@latest/dist/jspdf.min.js"></script>
    <script>
      (function generatePdf() {
        const doc = new jsPDF();
        doc.text("Hello semlinker!", 66, 88);
        const blob = new Blob([doc.output()], { type: "application/pdf" });
        blob.text().then((blobAsText) => {
          console.log(blobAsText);
        });
      })();
    </script>
  </body>
</html>

在以上示例中,我们首先创建 PDF 文档对象,然后调用该对象上的 text() 方法在指定的坐标点上添加 Hello semlinker! 文本,然后我们利用生成的 PDF 内容来创建对应的 Blob 对象,需要注意的是我们设置 Blob 的类型为 application/pdf,最后我们把 Blob 对象中保存的内容转换为文本并输出到控制台。由于内容较多,这里我们只列出少部分输出结果:

%PDF-1.3
%ºß¬à
3 0 obj
<</Type /Page
/Parent 1 0 R
/Resources 2 0 R
/MediaBox [0 0 595.28 841.89]
/Contents 4 0 R
>>
endobj
....

其实 jsPDF 除了支持纯文本之外,它也可以生成带图片的 PDF 文档,比如:

let imgData = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/...'
let doc = new jsPDF();

doc.setFontSize(40)
doc.text(35, 25, 'Paranyan loves jsPDF')
doc.addImage(imgData, 'JPEG', 15, 40, 180, 160)

Blob 的应用场景还很多,这里我们就不一一列举了,感兴趣的小伙伴可以自行查阅相关资料。

四、Blob 与 ArrayBuffer 的区别

ArrayBuffer 对象用于表示通用的,固定长度的原始二进制数据缓冲区。你不能直接操纵 ArrayBuffer 的内容,而是需要创建一个类型化数组对象或 DataView 对象,该对象以特定格式表示缓冲区,并使用该对象读取和写入缓冲区的内容。

Blob 类型的对象表示不可变的类似文件对象的原始数据。Blob 表示的不一定是 JavaScript 原生格式的数据。File 接口基于 Blob,继承了Blob 功能并将其扩展为支持用户系统上的文件。

4.1 Blob vs ArrayBuffer

  • 除非你需要使用 ArrayBuffer 提供的写入/编辑的能力,否则 Blob 格式可能是最好的。
  • Blob 对象是不可变的,而 ArrayBuffer 是可以通过 TypedArrays 或 DataView 来操作。
  • ArrayBuffer 是存在内存中的,可以直接操作。而 Blob 可以位于磁盘、高速缓存内存和其他不可用的位置。
  • 虽然 Blob 可以直接作为参数传递给其他函数,比如 window.URL.createObjectURL()。但是,你可能仍需要 FileReader 之类的 File API 才能与 Blob 一起使用。
  • Blob 与 ArrayBuffer 对象之间是可以相互转化的:

    • 使用 FileReader 的 readAsArrayBuffer() 方法,可以把 Blob 对象转换为 ArrayBuffer 对象;
    • 使用 Blob 构造函数,如 new Blob([new Uint8Array(data]);,可以把 ArrayBuffer 对象转换为 Blob 对象。

对于 HTTP 的场景,比如在 AJAX 场景下,BlobArrayBuffer 可以通过以下方式来使用:

function GET(url, callback) {
  let xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'arraybuffer'; // or xhr.responseType = "blob";
  xhr.send();

  xhr.onload = function(e) {
    if (xhr.status != 200) {
      alert("Unexpected status code " + xhr.status + " for " + url);
      return false;
    }
    callback(new Uint8Array(xhr.response)); // or new Blob([xhr.response]);
  };
}

了解完上述的内容,相信有的读者可能会觉得意犹未尽。那么,对于 Blob 来说还有哪些内容可以继续深入学习的呢?本人下一步的计划是基于 Deno 的源码,来逐步分析 DenoBlob 的具体实现。当然也会顺便分析一下 URL.createObjectURL() 方法和 revokeObjectURL() 方法的实现。

五、参考资源

本人的全栈修仙之路订阅号,会定期分享 Angular、TypeScript、Node.js 相关文章,欢迎感兴趣的小伙伴订阅哈!

learned-ts.jpg

查看原文

青岛小龟 收藏了文章 · 2020-05-14

必须要会的 50 个React 面试题

翻译:疯狂的技术宅
原文:https://www.edureka.co/blog/i...

本文首发微信公众号:jingchengyideng
欢迎关注,每天都给你推送新鲜的前端技术文章


如果你是一位有抱负的前端程序员并准备面试,那么这篇文章很适合你。本文是你学习和面试 React 所需知识的完美指南。

JavaScript 工具缓慢而稳定地在市场中扎根,对 React 的需求呈指数级增长。选择合适的技术来开发应用或网站变得越来越有挑战性。其中 React 被认为是增长最快的 Javascript 框架。

截至今天,Github 上约有1,000名贡献者。 Virtual DOM 和可重用组件等独特功能吸引了前端开发人员的注意力。尽管它只是 MVC(模型 - 视图 - 控制器)中“视图”的库,但它对 Angular,Meteor,Vue 等全面的框架也构成了强力的挑战。下图为流行的 JS 框架的趋势:

clipboard.png

JS 框架的趋势

React 面试题

以下是面试官最有可能问到的 50 个 React 面试题和答案。为方便你学习,我对它们进行了分类:

  • 基本知识
  • React 组件
  • React Redux
  • React 路由

基本知识

1. 区分Real DOM和Virtual DOM

Real DOMVirtual DOM
1. 更新缓慢。1. 更新更快。
2. 可以直接更新 HTML。2. 无法直接更新 HTML。
3. 如果元素更新,则创建新DOM。3. 如果元素更新,则更新 JSX 。
4. DOM操作代价很高。4. DOM 操作非常简单。
5. 消耗的内存较多。5. 很少的内存消耗。

2. 什么是React?

  • React 是 Facebook 在 2011 年开发的前端 JavaScript 库。
  • 它遵循基于组件的方法,有助于构建可重用的UI组件。
  • 它用于开发复杂和交互式的 Web 和移动 UI。
  • 尽管它仅在 2015 年开源,但有一个很大的支持社区。

3. React有什么特点?

React的主要功能如下:

  1. 它使用虚拟DOM 而不是真正的DOM。
  2. 它可以进行服务器端渲染
  3. 它遵循单向数据流或数据绑定。

4. 列出React的一些主要优点。

React的一些主要优点是:

  1. 它提高了应用的性能
  2. 可以方便地在客户端和服务器端使用
  3. 由于 JSX,代码的可读性很好
  4. React 很容易与 Meteor,Angular 等其他框架集成
  5. 使用React,编写UI测试用例变得非常容易

5. React有哪些限制?

React的限制如下:

  1. React 只是一个库,而不是一个完整的框架
  2. 它的库非常庞大,需要时间来理解
  3. 新手程序员可能很难理解
  4. 编码变得复杂,因为它使用内联模板和 JSX

6. 什么是JSX?

JSX 是J avaScript XML 的简写。是 React 使用的一种文件,它利用 JavaScript 的表现力和类似 HTML 的模板语法。这使得 HTML 文件非常容易理解。此文件能使应用非常可靠,并能够提高其性能。下面是JSX的一个例子:

render(){
    return(        
        <div>
            <h1> Hello World from Edureka!!</h1>
        </div>
    );
}

7. 你了解 Virtual DOM 吗?解释一下它的工作原理。

Virtual DOM 是一个轻量级的 JavaScript 对象,它最初只是 real DOM 的副本。它是一个节点树,它将元素、它们的属性和内容作为对象及其属性。 React 的渲染函数从 React 组件中创建一个节点树。然后它响应数据模型中的变化来更新该树,该变化是由用户或系统完成的各种动作引起的。

Virtual DOM 工作过程有三个简单的步骤。

  1. 每当底层数据发生改变时,整个 UI 都将在 Virtual DOM 描述中重新渲染。

clipboard.png

  1. 然后计算之前 DOM 表示与新表示的之间的差异。

clipboard.png

  1. 完成计算后,将只用实际更改的内容更新 real DOM。

clipboard.png

8. 为什么浏览器无法读取JSX?

浏览器只能处理 JavaScript 对象,而不能读取常规 JavaScript 对象中的 JSX。所以为了使浏览器能够读取 JSX,首先,需要用像 Babel 这样的 JSX 转换器将 JSX 文件转换为 JavaScript 对象,然后再将其传给浏览器。

9. 与 ES5 相比,React 的 ES6 语法有何不同?

以下语法是 ES5 与 ES6 中的区别:

1.require 与 import

// ES5
var React = require('react');
 
// ES6
import React from 'react';

2.export 与 exports

// ES5
module.exports = Component;
 
// ES6
export default Component;

3.component 和 function

// ES5
var MyComponent = React.createClass({
    render: function() {
        return
            <h3>Hello Edureka!</h3>;
    }
});
 
// ES6
class MyComponent extends React.Component {
    render() {
        return
            <h3>Hello Edureka!</h3>;
    }
}

4.props

// ES5
var App = React.createClass({
    propTypes: { name: React.PropTypes.string },
    render: function() {
        return
            <h3>Hello, {this.props.name}!</h3>;
    }
});

// ES6
class App extends React.Component {
    render() {
        return
            <h3>Hello, {this.props.name}!</h3>;
    }
}

5.state

// ES5
var App = React.createClass({
    getInitialState: function() {
        return { name: 'world' };
    },
    render: function() {
        return
            <h3>Hello, {this.state.name}!</h3>;
    }
});

// ES6
class App extends React.Component {
    constructor() {
        super();
        this.state = { name: 'world' };
    }
    render() {
        return
            <h3>Hello, {this.state.name}!</h3>;
    }
}

10. React与Angular有何不同?

主题ReactAngular
1. 体系结构只有 MVC 中的 View完整的 MVC
2. 渲染可以进行服务器端渲染客户端渲染
3. DOM使用 virtual DOM使用 real DOM
4. 数据绑定单向数据绑定双向数据绑定
5. 调试编译时调试运行时调试
6. 作者FacebookGoogle

React 组件

11. 你怎样理解“在React中,一切都是组件”这句话。

组件是 React 应用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的部分。每个组件彼此独立,而不会影响 UI 的其余部分。

12. 怎样解释 React 中 render() 的目的。

每个React组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示。如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 <form><group><div> 等。此函数必须保持纯净,即必须每次调用时都返回相同的结果。

13. 如何将两个或多个组件嵌入到一个组件中?

可以通过以下方式将组件嵌入到一个组件中:

class MyComponent extends React.Component{
    render(){
        return(          
            <div>
                <h1>Hello</h1>
                <Header/>
            </div>
        );
    }
}
class Header extends React.Component{
    render(){
        return
            <h1>Header Component</h1>   
   };
}
ReactDOM.render(
    <MyComponent/>, document.getElementById('content')
);

14. 什么是 Props?

Props 是 React 中属性的简写。它们是只读组件,必须保持纯,即不可变。它们总是在整个应用中从父组件传递到子组件。子组件永远不能将 prop 送回父组件。这有助于维护单向数据流,通常用于呈现动态生成的数据。

15. React中的状态是什么?它是如何使用的?

状态是 React 组件的核心,是数据的来源,必须尽可能简单。基本上状态是确定组件呈现和行为的对象。与props 不同,它们是可变的,并创建动态和交互式组件。可以通过 this.state() 访问它们。

16. 区分状态和 props

条件StateProps
1. 从父组件中接收初始值YesYes
2. 父组件可以改变值NoYes
3. 在组件中设置默认值YesYes
4. 在组件的内部变化YesNo
5. 设置子组件的初始值YesYes
6. 在子组件的内部更改NoYes

17. 如何更新组件的状态?

可以用 this.setState()更新组件的状态。

class MyComponent extends React.Component {
    constructor() {
        super();
        this.state = {
            name: 'Maxx',
            id: '101'
        }
    }
    render()
        {
            setTimeout(()=>{this.setState({name:'Jaeha', id:'222'})},2000)
            return (              
                <div>
                    <h1>Hello {this.state.name}</h1>
                    <h2>Your Id is {this.state.id}</h2>
                </div>
            );
        }
    }
ReactDOM.render(
    <MyComponent/>, document.getElementById('content')
);

18. React 中的箭头函数是什么?怎么用?

箭头函数(=>)是用于编写函数表达式的简短语法。这些函数允许正确绑定组件的上下文,因为在 ES6 中默认下不能使用自动绑定。使用高阶函数时,箭头函数非常有用。

//General way
render() {    
    return(
        <MyInput onChange = {this.handleChange.bind(this) } />
    );
}
//With Arrow Function
render() {  
    return(
        <MyInput onChange = { (e)=>this.handleOnChange(e) } />
    );
}

19. 区分有状态和无状态组件。

有状态组件无状态组件
1. 在内存中存储有关组件状态变化的信息1. 计算组件的内部的状态
2. 有权改变状态2. 无权改变状态
3. 包含过去、现在和未来可能的状态变化情况3. 不包含过去,现在和未来可能发生的状态变化情况
4. 接受无状态组件状态变化要求的通知,然后将 props 发送给他们。4.从有状态组件接收 props 并将其视为回调函数。

20. React组件生命周期的阶段是什么?

React 组件的生命周期有三个不同的阶段:

  1. 初始渲染阶段:这是组件即将开始其生命之旅并进入 DOM 的阶段。
  2. 更新阶段:一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才可能更新和重新渲染。这些只发生在这个阶段。
  3. 卸载阶段:这是组件生命周期的最后阶段,组件被销毁并从 DOM 中删除。

21. 详细解释 React 组件的生命周期方法。

一些最重要的生命周期方法是:

  1. componentWillMount() – 在渲染之前执行,在客户端和服务器端都会执行。
  2. componentDidMount() – 仅在第一次渲染后在客户端执行。
  3. componentWillReceiveProps() – 当从父类接收到 props 并且在调用另一个渲染器之前调用。
  4. shouldComponentUpdate() – 根据特定条件返回 true 或 false。如果你希望更新组件,请返回true 否则返回 false。默认情况下,它返回 false。
  5. componentWillUpdate() – 在 DOM 中进行渲染之前调用。
  6. componentDidUpdate() – 在渲染发生后立即调用。
  7. componentWillUnmount() – 从 DOM 卸载组件后调用。用于清理内存空间。

22. React中的事件是什么?

在 React 中,事件是对鼠标悬停、鼠标单击、按键等特定操作的触发反应。处理这些事件类似于处理 DOM 元素中的事件。但是有一些语法差异,如:

  1. 用驼峰命名法对事件命名而不是仅使用小写字母。
  2. 事件作为函数而不是字符串传递。

事件参数重包含一组特定于事件的属性。每个事件类型都包含自己的属性和行为,只能通过其事件处理程序访问。

23. 如何在React中创建一个事件?

class Display extends React.Component({    
    show(evt) {
        // code   
    },   
    render() {      
        // Render the div with an onClick prop (value is a function)        
        return (            
            <div onClick={this.show}>Click Me!</div>
        );    
    }
});

24. React中的合成事件是什么?

合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同浏览器的行为合并为一个 API。这样做是为了确保事件在不同浏览器中显示一致的属性。

25. 你对 React 的 refs 有什么了解?

Refs 是 React 中引用的简写。它是一个有助于存储对特定的 React 元素或组件的引用的属性,它将由组件渲染配置函数返回。用于对 render() 返回的特定元素或组件的引用。当需要进行 DOM 测量或向组件添加方法时,它们会派上用场。

class ReferenceDemo extends React.Component{
     display() {
         const name = this.inputDemo.value;
         document.getElementById('disp').innerHTML = name;
     }
render() {
    return(        
          <div>
            Name: <input type="text" ref={input => this.inputDemo = input} />
            <button name="Click" onClick={this.display}>Click</button>            
            <h2>Hello <span id="disp"></span> !!!</h2>
          </div>
    );
   }
 }

26. 列出一些应该使用 Refs 的情况。

以下是应该使用 refs 的情况:

  • 需要管理焦点、选择文本或媒体播放时
  • 触发式动画
  • 与第三方 DOM 库集成

27. 如何模块化 React 中的代码?

可以使用 export 和 import 属性来模块化代码。它们有助于在不同的文件中单独编写组件。

//ChildComponent.jsx
export default class ChildComponent extends React.Component {
    render() {
        return(           
              <div>
                  <h1>This is a child component</h1>
              </div>
        );
    }
}
 
//ParentComponent.jsx
import ChildComponent from './childcomponent.js';
class ParentComponent extends React.Component {    
    render() {        
        return(           
             <div>               
                <App />          
             </div>       
        );  
    }
}

28. 如何在 React 中创建表单

React 表单类似于 HTML 表单。但是在 React 中,状态包含在组件的 state 属性中,并且只能通过 setState() 更新。因此元素不能直接更新它们的状态,它们的提交是由 JavaScript 函数处理的。此函数可以完全访问用户输入到表单的数据。

handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
}
 
render() {
    return (        
        <form onSubmit={this.handleSubmit}>
            <label>
                Name:
                <input type="text" value={this.state.value} onChange={this.handleSubmit} />
            </label>
            <input type="submit" value="Submit" />
        </form>
    );
}

29. 你对受控组件和非受控组件了解多少?

受控组件非受控组件
1. 没有维持自己的状态1. 保持着自己的状态
2.数据由父组件控制2.数据由 DOM 控制
3. 通过 props 获取当前值,然后通过回调通知更改3. Refs 用于获取其当前值

30. 什么是高阶组件(HOC)?

高阶组件是重用组件逻辑的高级方法,是一种源于 React 的组件模式。 HOC 是自定义组件,在它之内包含另一个组件。它们可以接受子组件提供的任何动态,但不会修改或复制其输入组件中的任何行为。你可以认为 HOC 是“纯(Pure)”组件。

31. 你能用HOC做什么?

HOC可用于许多任务,例如:

  • 代码重用,逻辑和引导抽象
  • 渲染劫持
  • 状态抽象和控制
  • Props 控制

32. 什么是纯组件?

纯(Pure) 组件是可以编写的最简单、最快的组件。它们可以替换任何只有 render() 的组件。这些组件增强了代码的简单性和应用的性能。

33. React 中 key 的重要性是什么?

key 用于识别唯一的 Virtual DOM 元素及其驱动 UI 的相应数据。它们通过回收 DOM 中当前所有的元素来帮助 React 优化渲染。这些 key 必须是唯一的数字或字符串,React 只是重新排序元素而不是重新渲染它们。这可以提高应用程序的性能。

React Redux

34. MVC框架的主要问题是什么?

以下是MVC框架的一些主要问题:

  • 对 DOM 操作的代价非常高
  • 程序运行缓慢且效率低下
  • 内存浪费严重
  • 由于循环依赖性,组件模型需要围绕 models 和 views 进行创建

35. 解释一下 Flux

clipboard.png

Flux 是一种强制单向数据流的架构模式。它控制派生数据,并使用具有所有数据权限的中心 store 实现多个组件之间的通信。整个应用中的数据更新必须只能在此处进行。 Flux 为应用提供稳定性并减少运行时的错误。

36. 什么是Redux?

Redux 是当今最热门的前端开发库之一。它是 JavaScript 程序的可预测状态容器,用于整个应用的状态管理。使用 Redux 开发的应用易于测试,可以在不同环境中运行,并显示一致的行为。

37. Redux遵循的三个原则是什么?

  1. 单一事实来源:整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。
  2. 状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。
  3. 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数。

clipboard.png

38. 你对“单一事实来源”有什么理解?

Redux 使用 “Store” 将程序的整个状态存储在同一个地方。因此所有组件的状态都存储在 Store 中,并且它们从 Store 本身接收更新。单一状态树可以更容易地跟踪随时间的变化,并调试或检查程序。

39. 列出 Redux 的组件。

Redux 由以下组件组成:

  1. Action – 这是一个用来描述发生了什么事情的对象。
  2. Reducer – 这是一个确定状态将如何变化的地方。
  3. Store – 整个程序的状态/对象树保存在Store中。
  4. View – 只显示 Store 提供的数据。

40. 数据如何通过 Redux 流动?

clipboard.png

41. 如何在 Redux 中定义 Action?

React 中的 Action 必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,并且还可以向其添加更多的属性。在 Redux 中,action 被名为 Action Creators 的函数所创建。以下是 Action 和Action Creator 的示例:

function addTodo(text) {
       return {
                type: ADD_TODO,    
                 text
    }
}

42. 解释 Reducer 的作用。

Reducers 是纯函数,它规定应用程序的状态怎样因响应 ACTION 而改变。Reducers 通过接受先前的状态和 action 来工作,然后它返回一个新的状态。它根据操作的类型确定需要执行哪种更新,然后返回新的值。如果不需要完成任务,它会返回原来的状态。

43. Store 在 Redux 中的意义是什么?

Store 是一个 JavaScript 对象,它可以保存程序的状态,并提供一些方法来访问状态、调度操作和注册侦听器。应用程序的整个状态/对象树保存在单一存储中。因此,Redux 非常简单且是可预测的。我们可以将中间件传递到 store 来处理数据,并记录改变存储状态的各种操作。所有操作都通过 reducer 返回一个新状态。

44. Redux与Flux有何不同?

FluxRedux
1. Store 包含状态和更改逻辑1. Store 和更改逻辑是分开的
2. 有多个 Store2. 只有一个 Store
3. 所有 Store 都互不影响且是平级的3. 带有分层 reducer 的单一 Store
4. 有单一调度器4. 没有调度器的概念
5. React 组件订阅 store5. 容器组件是有联系的
6. 状态是可变的6. 状态是不可改变的

45. Redux 有哪些优点?

Redux 的优点如下:

  • 结果的可预测性 - 由于总是存在一个真实来源,即 store ,因此不存在如何将当前状态与动作和应用的其他部分同步的问题。
  • 可维护性 - 代码变得更容易维护,具有可预测的结果和严格的结构。
  • 服务器端渲染 - 你只需将服务器上创建的 store 传到客户端即可。这对初始渲染非常有用,并且可以优化应用性能,从而提供更好的用户体验。
  • 开发人员工具 - 从操作到状态更改,开发人员可以实时跟踪应用中发生的所有事情。
  • 社区和生态系统 - Redux 背后有一个巨大的社区,这使得它更加迷人。一个由才华横溢的人组成的大型社区为库的改进做出了贡献,并开发了各种应用。
  • 易于测试 - Redux 的代码主要是小巧、纯粹和独立的功能。这使代码可测试且独立。
  • 组织 - Redux 准确地说明了代码的组织方式,这使得代码在团队使用时更加一致和简单。

React 路由

46. 什么是React 路由?

React 路由是一个构建在 React 之上的强大的路由库,它有助于向应用程序添加新的屏幕和流。这使 URL 与网页上显示的数据保持同步。它负责维护标准化的结构和行为,并用于开发单页 Web 应用。 React 路由有一个简单的API。

47. 为什么React Router v4中使用 switch 关键字 ?

虽然 <div> 用于封装 Router 中的多个路由,当你想要仅显示要在多个定义的路线中呈现的单个路线时,可以使用 “switch” 关键字。使用时,<switch> 标记会按顺序将已定义的 URL 与已定义的路由进行匹配。找到第一个匹配项后,它将渲染指定的路径。从而绕过其它路线。

48. 为什么需要 React 中的路由?

Router 用于定义多个路由,当用户定义特定的 URL 时,如果此 URL 与 Router 内定义的任何 “路由” 的路径匹配,则用户将重定向到该特定路由。所以基本上我们需要在自己的应用中添加一个 Router 库,允许创建多个路由,每个路由都会向我们提供一个独特的视图

<switch>
    <route exact path=’/’ component={Home}/>
    <route path=’/posts/:id’ component={Newpost}/>
    <route path=’/posts’   component={Post}/>
</switch>

49. 列出 React Router 的优点。

几个优点是:

  1. 就像 React 基于组件一样,在 React Router v4 中,API 是 'All About Components'。可以将 Router 可视化为单个根组件(<BrowserRouter>),其中我们将特定的子路由(<route>)包起来。
  2. 无需手动设置历史值:在 React Router v4 中,我们要做的就是将路由包装在 <BrowserRouter> 组件中。
  3. 包是分开的:共有三个包,分别用于 Web、Native 和 Core。这使我们应用更加紧凑。基于类似的编码风格很容易进行切换。

50. React Router与常规路由有何不同?

主题常规路由React 路由
参与的页面每个视图对应一个新文件只涉及单个HTML页面
URL 更改HTTP 请求被发送到服务器并且接收相应的 HTML 页面仅更改历史记录属性
体验用户实际在每个视图的不同页面切换用户认为自己正在不同的页面间切换

希望这套 React 面试题和答案能帮你准备面试。祝一切顺利!


本文首发微信公众号:jingchengyideng

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:

查看原文

青岛小龟 赞了文章 · 2018-12-28

简约强大数组操作组合

前言

在实际js开发中对数组操作频率非常高,看过一些小伙伴的一些用法,挺有意思,在这里小记(不全)一下,备忘。

array-combination

5个迭代方法:every、filter、forEach、map和some

  • every():对数组中的每一项运行给定函数,如果该函数每一项都返回true,则返回true
  • filter():对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组;
  • forEach():对数组中的每一项运行给定函数,这个方法没有返回值;
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组;
  • some():对数组中的每一项运行给定函数,如果该函数任意一项返回true,则返回true
// every
var numbers = [1, 2, 3, 4, 5, 6, 7];
var everyResult = numbers.every(function (item, index, array) {
  return (item > 2);
});
console.log(everyResult); // false

// some
someResult = numbers.some(function (item, index, array) {
  return (item > 2);
});
console.log(someResult); // true

// filter
var filterResult = numbers.filter(function (item, index, array) {
  return (item > 2);
});
console.log(filterResult); // [3, 4, 5, 6, 7]

// map
var mapResult = numbers.map(function (item, index, array) {
  return item * 2;
});
console.log(mapResult); // [2, 4, 6, 8, 10, 12, 14]

被忽视的 map 的第二个、第三个参数

通常情况下, map 方法中的 callback 函数只接受一个参数,就是正在被遍历数组元素本身。但不意味着 map 只给 callback 传一个参数,这种惯性思维很可能会让我们犯错。下面举一个例子:

下面语句返回什么呢:['1', '2', '3'].map(parseInt)

可能你会觉得是 [1, 2, 3],但实际结果是 [1, NaN, NaN]

map 回调方法 callback(currentValue, index, array) 有三个参数,第一个是数组中正在处理的当前元素,第二个是当前元素索引,第三个是数组本身。

Number.parseInt(string[, radix])有两个参数,第一个是待转化字符,第二个是进制数。parseInt传入第三个参数会被忽略。

因此,上述执行

parseInt('1', 0, ['1', '2', '3']) // 1
parseInt('2', 1, ['1', '2', '3']) // NaN
parseInt('3', 2, ['1', '2', '3']) // NaN

拓展 map 在实际项目中的应用

匹配查找某个目录下的文件并引入。

context.require 返回一个 require 函数:

function webpackContext(req) {
  return __webpack_require__(webpackContextResolve(req));
}

该函数有一个 keys 属性,是一个函数,返回一个数组,该数组是由所有可能被上下文模块的请求对象组成。

let requireAll = requireContext => requireContext.keys().map(requireContext)
let req = require.context('./svg', false, /\.svg$/)
requireAll(req)

这样通过 map 遍历,结合引入上下文对象作为回调函数,即可获取引入某个目录下的 svg 资源。

一个归并方法:reduce

array.reduce(callback[, initialValue])第一个参数是每一项上调用的函数,该函数有四个参数:

  1. accumulator:累加回调返回值;他是上一次调用时返回的累积值,或initValue
  2. currentValue:数组中正在处理的元素;
  3. currentIndex:数组中正在处理的当前元素的索引。如果提供了initialValue,这索引号为0,否则索引为1;
  4. array:调用reduce()的数组。

当第二个参数省略时,遍历从数组第二项开始,数组第一项被当作前一个值accumulator

数组求和

const numbers = [10, 20, 30, 40];
numbers.reduce((acc, cur, index, arr) => {
  console.log('acc: ' + acc + '; ' + 'cur: ' + cur + ';');
  return acc + cur;
})

结果为:

acc: 10; cur: 20;
acc: 30; cur: 30;
acc: 60; cur: 40;

这第二个参数就是设置accumulator的初始类型和初始值,比如为0,就表示accumulator的初始值为Number类型,值为0,因此,reduce的最终结果也会是Number类型。

const numbers = [10, 20, 30, 40];
numbers.reduce((acc, cur, index, arr) => {
  console.log('acc: ' + acc + '; ' + 'cur: ' + cur + ';');
  return acc + cur;
}, 0)

结果为:

acc: 0; cur: 10;
acc: 10; cur: 20;
acc: 30; cur: 30;
acc: 60; cur: 40;

强大的reduce

reduce作为归并方法,在有些情形可以替代其它数组操作方法,强大之处,还得要落实在具体的案例上。

假设现在有一个数列[10, 20, 30, 40, 50],每一项乘以2,然后筛选出大于60的项。

在这里更新数组每一项(map的功能)然后筛选出一部分(filter的功能),如果是先使用map然后filter的话,你需要遍历这个数组两次。在这里用reduce更高效。

var numbers = [10, 20, 30, 40, 50];
var result = numbers.reduce(function (acc, cur) {
  cur = cur * 2;
  if (cur > 60) {
    acc.push(cur);
  }
  return acc;
}, []);
console.log(result); // [80, 100]

从这个例子可以看出reduce完成了mapfilter的使命。

统计数组中重复出现项的个数,用对象表示。
var letters = ['A', 'B', 'C', 'C', 'B', 'C', 'C'];
var letterObj = letters.reduce(function (acc, cur) {
  acc[cur] = acc[cur] ? ++acc[cur] : 1;
  return acc;
}, {});
console.log(letterObj); // {A: 1, B: 2, C: 4}
数组去重
var letters = ['A', 'B', 'C', 'C', 'B', 'C', 'C'];
var letterArr = letters.reduce(function (acc, cur) {
  if (acc.indexOf(cur) === -1) {
    acc.push(cur);
  }
  return acc;
}, []);
console.log(letterArr); //  ["A", "B", "C"]

ps:了解更多数组去重方法,点这里

与ES6的结合

与ES6结合使用也会擦出不少火花。

删除目标对象某个属性。
const person = {
  name: 'jazz',
  gender: 'male',
  age: 24
};
const personUnknowAge = Object.keys(person).filter((key) => {
  return key !== 'age';
})
.map((key) => {
  return {
    [key]: person[key]
  }
})
.reduce((acc, cur) => {
  return {...acc, ...cur};
}, {});
console.log(personUnknowAge); // {name: "jazz", gender: "male"}

更简洁的方案,利用ES6中函数参数解构:

const personUnknowAge = (({name, gender}) => ({name, gender}))(person);
console.log(personUnknowAge); // {name: "jazz", gender: "male"}

当然还有更简单的方案,利用ES6中对象解构:

const person = {
  name: 'jazz',
  gender: 'male',
  age: 24
};
let { age, ...personUnknowAge } = person;
console.log(personUnknowAge); // {name: "jazz", gender: "male"}

结尾

数组操作的“妙用”远不止这些,后面有空再研究补充吧,完~

查看原文

赞 114 收藏 83 评论 2

青岛小龟 收藏了文章 · 2018-12-28

简约强大数组操作组合

前言

在实际js开发中对数组操作频率非常高,看过一些小伙伴的一些用法,挺有意思,在这里小记(不全)一下,备忘。

array-combination

5个迭代方法:every、filter、forEach、map和some

  • every():对数组中的每一项运行给定函数,如果该函数每一项都返回true,则返回true
  • filter():对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组;
  • forEach():对数组中的每一项运行给定函数,这个方法没有返回值;
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组;
  • some():对数组中的每一项运行给定函数,如果该函数任意一项返回true,则返回true
// every
var numbers = [1, 2, 3, 4, 5, 6, 7];
var everyResult = numbers.every(function (item, index, array) {
  return (item > 2);
});
console.log(everyResult); // false

// some
someResult = numbers.some(function (item, index, array) {
  return (item > 2);
});
console.log(someResult); // true

// filter
var filterResult = numbers.filter(function (item, index, array) {
  return (item > 2);
});
console.log(filterResult); // [3, 4, 5, 6, 7]

// map
var mapResult = numbers.map(function (item, index, array) {
  return item * 2;
});
console.log(mapResult); // [2, 4, 6, 8, 10, 12, 14]

被忽视的 map 的第二个、第三个参数

通常情况下, map 方法中的 callback 函数只接受一个参数,就是正在被遍历数组元素本身。但不意味着 map 只给 callback 传一个参数,这种惯性思维很可能会让我们犯错。下面举一个例子:

下面语句返回什么呢:['1', '2', '3'].map(parseInt)

可能你会觉得是 [1, 2, 3],但实际结果是 [1, NaN, NaN]

map 回调方法 callback(currentValue, index, array) 有三个参数,第一个是数组中正在处理的当前元素,第二个是当前元素索引,第三个是数组本身。

Number.parseInt(string[, radix])有两个参数,第一个是待转化字符,第二个是进制数。parseInt传入第三个参数会被忽略。

因此,上述执行

parseInt('1', 0, ['1', '2', '3']) // 1
parseInt('2', 1, ['1', '2', '3']) // NaN
parseInt('3', 2, ['1', '2', '3']) // NaN

拓展 map 在实际项目中的应用

匹配查找某个目录下的文件并引入。

context.require 返回一个 require 函数:

function webpackContext(req) {
  return __webpack_require__(webpackContextResolve(req));
}

该函数有一个 keys 属性,是一个函数,返回一个数组,该数组是由所有可能被上下文模块的请求对象组成。

let requireAll = requireContext => requireContext.keys().map(requireContext)
let req = require.context('./svg', false, /\.svg$/)
requireAll(req)

这样通过 map 遍历,结合引入上下文对象作为回调函数,即可获取引入某个目录下的 svg 资源。

一个归并方法:reduce

array.reduce(callback[, initialValue])第一个参数是每一项上调用的函数,该函数有四个参数:

  1. accumulator:累加回调返回值;他是上一次调用时返回的累积值,或initValue
  2. currentValue:数组中正在处理的元素;
  3. currentIndex:数组中正在处理的当前元素的索引。如果提供了initialValue,这索引号为0,否则索引为1;
  4. array:调用reduce()的数组。

当第二个参数省略时,遍历从数组第二项开始,数组第一项被当作前一个值accumulator

数组求和

const numbers = [10, 20, 30, 40];
numbers.reduce((acc, cur, index, arr) => {
  console.log('acc: ' + acc + '; ' + 'cur: ' + cur + ';');
  return acc + cur;
})

结果为:

acc: 10; cur: 20;
acc: 30; cur: 30;
acc: 60; cur: 40;

这第二个参数就是设置accumulator的初始类型和初始值,比如为0,就表示accumulator的初始值为Number类型,值为0,因此,reduce的最终结果也会是Number类型。

const numbers = [10, 20, 30, 40];
numbers.reduce((acc, cur, index, arr) => {
  console.log('acc: ' + acc + '; ' + 'cur: ' + cur + ';');
  return acc + cur;
}, 0)

结果为:

acc: 0; cur: 10;
acc: 10; cur: 20;
acc: 30; cur: 30;
acc: 60; cur: 40;

强大的reduce

reduce作为归并方法,在有些情形可以替代其它数组操作方法,强大之处,还得要落实在具体的案例上。

假设现在有一个数列[10, 20, 30, 40, 50],每一项乘以2,然后筛选出大于60的项。

在这里更新数组每一项(map的功能)然后筛选出一部分(filter的功能),如果是先使用map然后filter的话,你需要遍历这个数组两次。在这里用reduce更高效。

var numbers = [10, 20, 30, 40, 50];
var result = numbers.reduce(function (acc, cur) {
  cur = cur * 2;
  if (cur > 60) {
    acc.push(cur);
  }
  return acc;
}, []);
console.log(result); // [80, 100]

从这个例子可以看出reduce完成了mapfilter的使命。

统计数组中重复出现项的个数,用对象表示。
var letters = ['A', 'B', 'C', 'C', 'B', 'C', 'C'];
var letterObj = letters.reduce(function (acc, cur) {
  acc[cur] = acc[cur] ? ++acc[cur] : 1;
  return acc;
}, {});
console.log(letterObj); // {A: 1, B: 2, C: 4}
数组去重
var letters = ['A', 'B', 'C', 'C', 'B', 'C', 'C'];
var letterArr = letters.reduce(function (acc, cur) {
  if (acc.indexOf(cur) === -1) {
    acc.push(cur);
  }
  return acc;
}, []);
console.log(letterArr); //  ["A", "B", "C"]

ps:了解更多数组去重方法,点这里

与ES6的结合

与ES6结合使用也会擦出不少火花。

删除目标对象某个属性。
const person = {
  name: 'jazz',
  gender: 'male',
  age: 24
};
const personUnknowAge = Object.keys(person).filter((key) => {
  return key !== 'age';
})
.map((key) => {
  return {
    [key]: person[key]
  }
})
.reduce((acc, cur) => {
  return {...acc, ...cur};
}, {});
console.log(personUnknowAge); // {name: "jazz", gender: "male"}

更简洁的方案,利用ES6中函数参数解构:

const personUnknowAge = (({name, gender}) => ({name, gender}))(person);
console.log(personUnknowAge); // {name: "jazz", gender: "male"}

当然还有更简单的方案,利用ES6中对象解构:

const person = {
  name: 'jazz',
  gender: 'male',
  age: 24
};
let { age, ...personUnknowAge } = person;
console.log(personUnknowAge); // {name: "jazz", gender: "male"}

结尾

数组操作的“妙用”远不止这些,后面有空再研究补充吧,完~

查看原文

认证与成就

  • 获得 24 次点赞
  • 获得 15 枚徽章 获得 0 枚金徽章, 获得 5 枚银徽章, 获得 10 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-12-30
个人主页被 357 人浏览