目
待续。。
1、变量解构赋值的用途 地址
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
(2)从函数返回多个值
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
(3)函数参数的定义
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
(4)提取 JSON 数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
(5)函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
} = {}) {
// ... do stuff
};
(6)遍历 Map 结构
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
(7)输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");
2、字符串中常用的方法 地址
这里只是一小部分我用到的,更详细的请到地址所在链接去看
1)includes(), startsWith(), endsWith()
可以用来确定一个字符串是否包含在另一个字符串中。
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
//这三个方法都支持第二个参数,表示开始搜索的位置
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
2)padStart(),padEnd()
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。
padStart()
用于头部补全,padEnd()
用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
//padStart()的常见用途是为数值补全指定位数
'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"
//另一个用途是提示字符串格式
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
3)trimStart(),trimEnd() ,trim()
ES2019 对字符串实例新增了
trimStart()
和trimEnd()
这两个方法。它们的行为与trim()
一致,trimStart()
消除字符串头部的空格,trimEnd()
消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。
浏览器还部署了额外的两个方法,
trimLeft()
是trimStart()
的别名,trimRight()
是trimEnd()
的别名
3、数组的扩展 地址
1)扩展运算符(...),个人感觉用好它,能省好多事(仅仅记录了我工作中用到的,更详细的请到地址链接去查看)
**复制数组
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
**合并数组
//注意都是浅拷贝
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
**与解构赋值结合
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"];
first // "foo"
rest // []
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const [...butLast, last] = [1, 2, 3, 4, 5]; // 报错 const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 报错
** Map 和 Set 结构(提到Set,顺嘴提一下,使用Array.from()+new Set()实现数组去重效果不错哦!前者负责将set对象转为数组,后者负责去重。)
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]
**Array.from---将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
对于还没有部署该方法的浏览器,可以用
Array.prototype.slice
方法替代。const toArray = (() => Array.from ? Array.from : obj => [].slice.call(obj) )();
//Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
//如果map函数里面用到了this关键字,还可以传入Array.from的第三个参数,用来绑定this。
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
**Array.of--用于将一组值,转换为数组
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
Array.of
基本上可以用来替代Array()
或new Array()
,并且不存在由于参数不同而导致的重载。它的行为非常统一
**数组实例的 find() 和 findIndex()
[1, 4, -5, 10].find((n) => n < 0)
// -5
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
1)数组实例的
find
方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true
的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
。2)数组实例的
findIndex
方法的用法与find
方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
。3)这两个方法都可以接受第二个参数,用来绑定回调函数的
this
对象。function f(v){ return v > this.age; } let person = {name: 'John', age: 20}; [10, 12, 26, 15].find(f, person); //find函数接收了第二个参数person对象,回调函数中的this对象指向person对象。
4) 这两个方法都可以发现
NaN
,弥补了数组的indexOf
方法的不足。[NaN].indexOf(NaN) // -1 [NaN].findIndex(y => Object.is(NaN, y)) // 0
**数组实例的 entries(),keys() 和 values()
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
ES6 提供三个新的方法——
entries()
,keys()
和values()
——用于遍历数组。它们都返回一个遍历器对象,可以用for...of
循环进行遍历,唯一的区别是keys()
是对键名的遍历、values()
是对键值的遍历,entries()
是对键值对的遍历。
**数组实例的 includes()---用来替换indexOf
//一个参数
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
//两个参数
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
//es5
if (arr.indexOf(el) !== -1) {
// ...
}
1)Array.prototype.includes
方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes
方法类似。2)该方法的第二个参数表示搜索的起始位置,默认为
0
。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4
,但数组长度为3
),则会重置为从0
开始。3)
indexOf
方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1
,表达起来不够直观。二是,它内部使用严格相等运算符(===
)进行判断,这会导致对NaN
的误判[NaN].indexOf(NaN) // -1 [NaN].includes(NaN) // true,includes使用的是不一样的判断算法,就没有这个问题
4) Map 和 Set 数据结构有一个
has
方法,需要注意与includes
区分
- Map 结构的
has
方法,是用来查找键名的,比如Map.prototype.has(key)
、WeakMap.prototype.has(key)
、Reflect.has(target, propertyKey)
。- Set 结构的
has
方法,是用来查找值的,比如Set.prototype.has(value)
、WeakSet.prototype.has(value)
。
4、对象的一些常用方法 地址
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]
Object.values(obj)
// ["bar", 42]
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
将对象转为Map结构
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
值对的数据结构还原为对象:Object.fromEntries()
// 例一
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
Object.fromEntries(entries)
// { foo: "bar", baz: 42 }
// 例二
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }
将查询字符串转为i数组:URLSearchParams对象+Object.fromEntries()
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }
5、Symbol的使用 地址
ES6 引入了一种新的原始数据类型
Symbol
,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined
、null
、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
let s = Symbol("foo"); //foo作为Symbol的参数(可不传),是对Symbol的描述。主要是为了在控制台输出时的区别
let s1 = Symbol("fool")
// Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
console.log(s === s1) //false
console.log(typeof s) //symbol
console.log(s.description ) //foo //es2019提出
// Symbol 值可以显式转为字符串,布尔值,但是不能转换为数值
console.log(String(s),Boolean(s)) //Symbol(foo) true
console.log(Number(s)) //Uncaught TypeError: Cannot convert a Symbol value to a number
// Symbol 值不能与其他类型的值进行运算,会报错
let str = ""
console.log(str+s) //ncaught TypeError: Cannot convert a Symbol value to a string
Symbol
函数前不能使用new
命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型
symbol的用途
**作为属性名--由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性
let str = Symbol()
let strf = Symbol()
// let a = {}
// a[str] = 'test'
//或
// let a = {}
// Object.defineProperty(a,str,{value:"test"})
// 或
let a = {
[str]: 'test',
[strf]() {
console.log("haha")
}
}
a[strf]() //haha
console.log(a[str]) //test
// Symbol 值作为对象属性名时,不能用点运算符
let str1 = Symbol()
a.str1 = "test1"
console.log(a['str1']) //test1
console.log(a[str1]) //undefined
// 用来定义自足常量,保证常用的值都是不相等的
const log = {}
log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
};
console.log(log.levels.DEBUG, 'debug message'); //Symbol(debug) "debug message"
console.log(log.levels.INFO, 'info message'); //Symbol(info) "info message"
点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个 Symbol 值
属性名的遍历
Object.getOwnPropertySymbols或者Reflect.ownKeys
const obj = {}
let a = Symbol('a')
let b = Symbol('b')
obj[a] = 'hello'
obj[b] = 'world'
const objSymbol = Object.getOwnPropertySymbols(obj)
console.log(objSymbol) //[Symbol(a), Symbol(b)]
console.log(Reflect.ownKeys(obj)) [Symbol(a), Symbol(b)]
Symbol 作为属性名,遍历对象的时候,该属性不会出现在
for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
Symbol.for()和Symbol.keyFor()
// 使用Symbol函数返回值不会相同,使用Symbol.for可以得到相同的Symbol值,且登记的名字是全局的。
let s1 = Symbol.for("foo")
let s2 = Symbol.for("foo")
console.log(s1 === s2) //true
console.log(Symbol.keyFor(s1)) //foo 返回一个已登记的 Symbol 类型值的key
Symbol.for()
不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key
是否已经存在,如果不存在才会新建一个值。
Symbol()
写法没有登记机制,所以每次调用都会返回一个不同的值
内置的symbol值--11个
**Symbol.hasInstance---指向一个内部方法
**
Symbol.isConcatSpreadable---
等于一个布尔值,表示该对象用于Array.prototype.concat()
时,是否可以展开**
Symbol.species---
指向一个构造函数。创建衍生对象时,会使用该属性**
Symbol.match---
指向一个函数。当执行str.match(myObject)
时,如果该属性存在,会调用它,返回该方法的返回值**
Symbol.replace---
指向一个方法,当该对象被String.prototype.replace
方法调用时,会返回该方法的返回值**
Symbol.search---
指向一个方法,当该对象被String.prototype.search
方法调用时,会返回该方法的返回值**
Symbol.split---
指向一个方法,当该对象被String.prototype.split
方法调用时,会返回该方法的返回值**
Symbol.iterator---
指向该对象的默认遍历器方法**
Symbol.toPrimitive---
指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值**
Symbol.toStringTag---
指向一个方法。在该对象上面调用Object.prototype.toString
方法时,如果这个属性存在,它的返回值会出现在toString
方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object]
或[object Array]
中object
后面的那个字符串**
Symbol.unscopables---
指向一个对象。该对象指定了使用with
关键字时,哪些属性会被with
环境排除
6、Set和Map
set我常用来做去重处理
// set结构--由于set的值是惟一的所以可以用它来去重
let arg1 = [1, 1, 1, 2, 3]
let arg2 = "ababcdabab"
console.log([...new Set(arg1)]) //数组去重
console.log([...new Set(arg2)].join("")) //字符串去重
// 对象去重
let arg = [{
id: 1,
text: "11"
}, {
id: 1,
text: "11"
}]
let quchong = (arg) => {
var unique = {};
arg.forEach(function (gpa) {
unique[JSON.stringify(gpa)] = gpa
});
arg = Object.keys(unique).map(function (u) {
return JSON.parse(u)
});
return Object.values(unique)
}
console.log(quchong(arg))
set实例的属性和方法
// 操作方法: add,delete,has,clear
let arg = [1, 2, 3, 4, 5, 6]
let testSet = new Set(arg)
testSet.add(7) //添加某个值
console.log(testSet)
testSet.delete(7) //删除某个值
console.log(testSet)
console.log(testSet.has(1)) //判断是否有某个值
testSet.clear() //清除所有值,没有返回值
console.log(testSet)
let arg2 = [{
id: parseInt(Math.random()),
text: "001"
}, {
id: parseInt(Math.random()),
text: "002"
}]
let testSet2 = new Set(arg2)
// 遍历操作--keys,values,entries,forEach
// 由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致
console.log(testSet2.keys())
console.log(testSet2.values())
console.log(testSet2.entries())
testSet2.forEach((value, key) => {
console.log(value, key) //key和value值一样
}, this)
// 使用for...of遍历
for (let item of testSet2) {
console.log(item)
}
// 在遍历中改变set原有的值,目前可通过以下两种间接实现
let testSet3 = new Set([1,2,3,4,5,6]);
testSet3 = new Set([...testSet3].map(val => val*3))
console.log(testSet3)
//或
testSet3 = new Set(Array.from(testSet3,val => val/3))
console.log(testSet3)
map的话,这边暂时没有用到的实例,了解的话可以到https://es6.ruanyifeng.com/#docs/set-map
7、Proxy的基本使用和它在vuejs3.0中的应用(Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty--面试)
在MDN上对proxy的定义为:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
比起这个定义我更喜欢es6上对它的跟该直白解释:Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
然而这些都可以用vue中的一句话概括:Proxy 是一个包含另一个对象或函数并允许你对其进行拦截的对象
proxy使用的语法:var proxy = new Proxy(target(索要拦截的目标对象), handler(也是对象,用来定制拦截行为));
基本使用:
let handler = {
get(target, propKey) {
return prop in target ? target[propKey] : 37;
}
}
let p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b) //1,undefined
console.log('c' in p, p.c)//false,37
Proxy 支持的拦截操作一览--13,具体的使用案例,如果你对proxy由一定的了解可以到https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy,这里介绍的更加简介
- get(target, propKey, receiver):拦截对象属性的读取,比如
proxy.foo
和proxy['foo']
。- set(target, propKey, value, receiver):拦截对象属性的设置,比如
proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。- has(target, propKey):拦截
propKey in proxy
的操作,返回一个布尔值。- deleteProperty(target, propKey):拦截
delete proxy[propKey]
的操作,返回一个布尔值。- ownKeys(target):拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。- getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。- defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。- preventExtensions(target):拦截
Object.preventExtensions(proxy)
,返回一个布尔值。- getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy)
,返回一个对象。- isExtensible(target):拦截
Object.isExtensible(proxy)
,返回一个布尔值。- setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。- apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。
看到proxy这一篇,突然想到了之前看到的一个面试题:参考文档,这里包括了vue中使用的解说
Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty(其实并没有抛弃,Vue 3 版本也使用了 Object.defineProperty
来支持 IE 浏览器。两者具有相同的 Surface API,但是 Proxy 版本更精简,同时提升了性能)
Object.defineProperty:
**Vue 无法检测到 property 的添加或删除
**Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
**Vue 不允许动态添加根级响应式 property,所以你必须在初始化实例前声明所有根级响应式 property,哪怕只是一个空值
proxy:
**可以劫持整个对象,并返回一个新对象
**有13种劫持操作