Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。 Vue3.0 中将会通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。
let p = new Proxy(target, handler);
target
代表需要添加代理的对象,handler
用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
let onWatch = (obj, setBind, getLogger) => { let handler = { set(target, property, value, receiver) { setBind(value, property); return Reflect.set(target, property, value); }, get(target, property, receiver) { getLogger(target, property); return Reflect.get(target, property, receiver); }, }; return new Proxy(obj, handler); }; let obj = { a: 1 }; let p = onWatch( obj, (v, property) => { console.log(`监听到属性${property}改变为${v}`); }, (target, property) => { console.log(`'${property}' = ${target[property]}`); } ); p.a = 2; // 控制台输出:监听到属性a改变 p.a; // 'a' = 2
自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了。
map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后返回一个新数组,原数组不发生改变。
map 的回调函数接受三个参数,分别是当前索引元素,索引,原数组
var arr = [1, 2, 3]; var arr2 = arr.map((item) => item + 1); arr; //[ 1, 2, 3 ] arr2; // [ 2, 3, 4 ]
['1', '2', '3'].map(parseInt); // -> [ 1, NaN, NaN ]
filter 的作用也是生成一个新数组,在遍历数组的时候将返回值为 true 的元素放入新数组,我们可以利用这个函数删除一些不需要的元素
filter 的回调函数接受三个参数,分别是当前索引元素,索引,原数组
reduce 可以将数组中的元素通过回调函数最终转换为一个值。 如果我们想实现一个功能将函数里的元素全部相加得到一个值,可能会这样写代码
const arr = [1, 2, 3]; let total = 0; for (let i = 0; i < arr.length; i++) { total += arr[i]; } console.log(total); //6
但是如果我们使用 reduce 的话就可以将遍历部分的代码优化为一行代码
const arr = [1, 2, 3]; const sum = arr.reduce((acc, current) => acc + current, 0); console.log(sum);
对于 reduce 来说,它接受两个参数,分别是回调函数和初始值,接下来我们来分解上述代码中 reduce 的过程
this
,arguments
__proto__
Promise
翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了。
当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的。
new Promise((resolve, reject) => { console.log('new Promise'); resolve('success'); }); console.log('finifsh'); // 先打印new Promise, 再打印 finifsh
Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装。
Promise.resolve(1) .then((res) => { console.log(res); // => 1 return 2; // 包装成 Promise.resolve(2) }) .then((res) => { console.log(res); // => 2 });
当然了,Promise 也很好地解决了回调地狱的问题
ajax(url) .then((res) => { console.log(res); return ajax(url1); }) .then((res) => { console.log(res); return ajax(url2); }) .then((res) => console.log(res));
其实它也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。
一个函数如果加上 async ,那么该函数就会返回一个 Promise
async function test() { return '1'; } console.log(test()); // -> Promise {<resolved>: "1"}
async 就是将函数返回值使用 Promise.resolve() 包裹了下,和 then 中处理返回值一样,并且 await 只能配套 async 使用。
async function test() { let value = await sleep(); }
async 和 await 可以说是异步终极解决方案了,相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码,毕竟写一大堆 then 也很恶心,并且也能优雅地解决回调地狱问题。
当然也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。
async function test() { // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式 // 如果有依赖性的话,其实就是解决回调地狱的例子了 await fetch(url); await fetch(url1); await fetch(url2); }
看一个使用 await 的例子:
let a = 0; let b = async () => { a = a + (await 10); console.log('2', a); }; b(); a++; console.log('1', a); //先输出 ‘1’, 1 //在输出 ‘2’, 10
上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator。
function wait() { return new Promise((resolve) => setTimeout(resolve, 1000)); } async function main() { console.time(); const x = wait(); const y = wait(); const z = wait(); await x; await y; await z; console.timeEnd(); } main();
答案: 输出耗时: 1 秒多一点点。 原因: 3 个 wait 函数在赋值的时候就已经开始执行了。
稍微改造一下就可以得到 3 * 1000 ms 以上的结果
function wait() { return new Promise((resolve) => setTimeout(resolve, 1000)); } async function main() { console.time(); const x = await wait(); const y = await wait(); const z = await wait(); console.timeEnd(); } main();
function* foo(x) { let y = 2 * (yield x + 1); let z = yield y / 3; return x + y + z; } let it = foo(5); console.log(it.next()); // => {value: 6, done: false} console.log(it.next(12)); // => {value: 8, done: false} console.log(it.next(13)); // => {value: 42, done: true}
首先 Generator 函数调用和普通函数不同,它会返回一个迭代器
当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6
当执行第二次 next 时,传入的参数等于上一个 yield 的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 _ 12,所以第二个 yield 等于 2 _ 12 / 3 = 8
当执行第三次 next 时,传入的参数会传递给 z,所以 z = 13, x = 5, y = 24,相加等于 42
当 yeild 产生一个值后,生成器的执行上下文就会从栈中弹出。但由于迭代器一直保持着队执行上下文的引用,上下文不会丢失,不会像普通函数一样执行完后上下文就被销毁
ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别
// 引入模块 API import XXX from './a.js'; import { XXX } from './a.js'; // 导出模块 API export function a() {} export default function () {}
ES 模块是官方标准,也是 JavaScript 语言明确的发展方向,而 CommonJS 模块是一种特殊的传统格式,在 ES 模块被提出之前做为暂时的解决方案。 ES 模块允许进行静态分析,从而实现像 tree-shaking 的优化,并提供诸如循环引用和动态绑定等高级功能。
私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。
一种做法是在命名上加以区别,即在函数名或属性名前加_
,但这并不安全,只是一种团队规范。
另一种方法就是索性将私有方法移出类,放到模块里,因为模块内部的所有方法都是对外可见的。
class Widget { foo(baz) { bar.call(this, baz); } // ... } function bar(baz) { return (this.snaf = baz); }
上面代码中,foo 是公开方法,内部调用了 bar.call(this, baz)。这使得 bar 实际上成为了当前模块的私有方法。
还有一种方法是利用Symbol 值的唯一性,将私有方法的名字命名为一个 Symbol 值。
const bar = Symbol('bar'); const snaf = Symbol('snaf'); export default class myClass { // 公有方法 foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return (this[snaf] = baz); } // ... }
上面代码中,bar 和 snaf 都是 Symbol 值,一般情况下无法获取到它们,因此达到了私有方法和私有属性的效果。但是也不是绝对不行,Reflect.ownKeys()依然可以拿到它们。
const inst = new myClass(); Reflect.ownKeys(myClass.prototype); // [ 'constructor', 'foo', Symbol(bar) ]
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
var obj = new Proxy( {}, { get: function (target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); }, set: function (target, key, value, receiver) { console.log(`setting ${key}!`); return Reflect.set(target, key, value, receiver); }, } );
Proxy 支持的拦截操作一览,一共 13 种。
本文作者:前端小毛
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!