一、基础

js 隐式转换规则

  1. null == undefined, 但和其他值比较就不再相等了。
  2. NaN == NaN, 不相等。
  3. 对象 == 字符串,对象的toString()转为字符串
  4. 对象 == 数字,对象的toString()转为字符串,Number(对象的字符串)
  5. 剩下的都是转为数字比较。

for in 和 for of 的区别

  1. afor in 首先遍历对象的属性名,再是对象原型中的属性和方法, //如果不想让其输出原型中的属性和方法,可以使用hasOwnProperty方法进行过滤。也可以用Object.keys()方法获取所有的自身可枚举属性组成的数组<br /> b、for of 遍历对象时,要用Object.keys()先转成迭代器对象,例子:<br />
  1. var student={
  2. name:'wujunchuan',
  3. age:22,
  4. locate:{
  5. country:'china'
  6. }
  7. }
  8. for(var key of Object.keys(student)){
  9. //使用Object.keys()方法获取对象key的数组
  10. console.log(key+": "+student[key]);
  11. }

案例解析

  1. var getNumbers = () => {
  2. return Promise.resolve([1, 2, 3])
  3. }
  4. var multi = num => {
  5. return new Promise((resolve, reject) => {
  6. setTimeout(() => {
  7. if (num) {
  8. resolve(num * num)
  9. } else {
  10. reject(new Error('num not specified'))
  11. }
  12. }, 1000)
  13. })
  14. }
  15. async function test () {
  16. var nums = await getNumbers()
  17. nums.forEach(async x => {
  18. var res = await multi(x)
  19. console.log(res)
  20. })
  21. }
  22. test()

希望每间隔 1 秒,然后依次输出 1,4,9 。结果是一次性输出1,4,9
解决方案

  1. 改造一下 forEach,确保每一个异步的回调执行完成后,才执行下一个

    1. async function asyncForEach(array, callback) {
    2. for (let index = 0; index < array.length; index++) {
    3. await callback(array[index], index, array)
    4. }
    5. }
    6. async function test () {
    7. var nums = await getNumbers()
    8. asyncForEach(nums, async x => {
    9. var res = await multi(x)
    10. console.log(res)
    11. })
    12. }
  2. 使用 for-of 替代 for-each。

    1. async function test () {
    2. var nums = await getNumbers()
    3. for(let x of nums) {
    4. var res = await multi(x)
    5. console.log(res)
    6. }
    7. }

二、编写规范

禁止副作用

  1. const persons = [{name: 'liu', age: 18}, {name: 'wang', age: 22}];
  2. // bad
  3. const newPersions = persons.map(k => {
  4. k.hobby = 'swimming';
  5. return k;
  6. });
  7. // good
  8. const newPersions = persons.map(k => ({
  9. ...k,
  10. hobby: 'swimming'
  11. }));
  1. // bad
  2. function addPersions(persions) {
  3. persions.push({
  4. name: '吴',
  5. age: 20
  6. });
  7. }
  8. addPersions(persons);
  9. // good
  10. function addPersions(persions) {
  11. return [...persions, {
  12. name: '吴',
  13. age: 20
  14. }]
  15. }
  16. addPersions(persons);
  1. const persons = [{name: 'liu', age: 18}, {name: 'wang', age: 22}];
  2. // bad
  3. function sortPersions(persions) {
  4. // sort有副作用,会改变原数组
  5. const sortArr = persions.sort((a, b) => b.age - a.age);
  6. return sortArr;
  7. }
  8. sortPersions(persons);
  9. // good
  10. function sortPersions(persions) {
  11. // sort有副作用,会改变原数组
  12. const sortArr = [...persions].sort((a, b) => b.age - a.age);
  13. return sortArr;
  14. }
  15. sortPersions(persons);
  1. // very bad
  2. const prev = { coffee: 1 };
  3. const next = Object.assign(prev, { pizza: 42 });
  4. // bad
  5. const prev = { coffee: 1 };
  6. const next = Object.assign({}, prev, { pizza: 42 });
  7. // good
  8. const prev = { coffee: 1 };
  9. const next = { ...prev, pizza: 42 };

禁止不必要的条件

  1. // bad
  2. const hasValue = value !== NONE ? true : false;
  3. const hasProducts = products.length > 0 ? true : false;
  4. // good
  5. const hasValue = value !== NONE;
  6. const hasProducts = products.length > 0;
  1. // bad
  2. const hasValue = value ? true : false;
  3. const hasProducts = products.length ? true : false;
  4. // good
  5. const hasValue = Boolean(value);
  1. // bad
  2. const hasProducts = products && Array.isArray(products);
  3. // good
  4. const hasProducts = Array.isArray(products);
  1. // bad
  2. function IsNetscapeOnSolaris() {
  3. var agent = window.navigator.userAgent;
  4. if (
  5. agent.indexOf('Mozilla') != -1 &&
  6. agent.indexOf('compatible') == -1
  7. ) {
  8. if (agent.indexOf('SunOS') != -1) return true;
  9. else return false;
  10. } else {
  11. return false;
  12. }
  13. }
  14. // good
  15. function IsNetscapeOnSolaris() {
  16. const { userAgent } = window.navigator;
  17. return (
  18. userAgent.includes('Mozilla') &&
  19. userAgent.includes('SunOS') &&
  20. !userAgent.includes('compatible')
  21. );
  22. }
  1. // bad
  2. if (errorMessage) {
  3. log(LOG_LEVEL.ERROR, errorMessage);
  4. } else {
  5. log(LOG_LEVEL.ERROR, DEFAULT_ERROR_MESSAGE);
  6. }
  7. // good
  8. log(LOG_LEVEL.ERROR, errorMessage || DEFAULT_ERROR_MESSAGE);
  1. // bad
  2. function getProductsDropdownItems(response) {
  3. const products = response.products;
  4. if (products.length > 0) {
  5. return products.map(product => ({
  6. label: product.name,
  7. value: product.id
  8. }));
  9. }
  10. return [];
  11. }
  12. // 优化后,减少了一层判断
  13. // 问题:当传入的products为undefined时,默认值才生效
  14. function getProductsDropdownItems(products = []) {
  15. return products.map(product => ({
  16. label: product.name,
  17. value: product.id
  18. }));
  19. }
  20. // 解决上面的问题
  21. function getProductsDropdownItems(products) {
  22. return (products || []).map(product => ({
  23. label: product.name,
  24. value: product.id
  25. }));
  26. }
  27. // 最终版本
  28. function getProductsDropdownItems({ products }) {
  29. (Array.isArray(products) ? products : [products]).map(product => ({
  30. label: product.name,
  31. value: product.id
  32. }));
  33. }
  1. // bad
  2. const generateOptionalRows = () => {
  3. const rows = [];
  4. if (product1.colors.length + product2.colors.length > 0) {
  5. rows.push({
  6. row: 'Colors',
  7. product1: <ProductOptions options={product1.colors} />,
  8. product2: <ProductOptions options={product2.colors} />
  9. });
  10. }
  11. if (product1.sizes.length + product2.sizes.length > 0) {
  12. rows.push({
  13. row: 'Sizes',
  14. product1: <ProductOptions options={product1.sizes} />,
  15. product2: <ProductOptions options={product2.sizes} />
  16. });
  17. }
  18. return rows;
  19. };
  20. const rows = [
  21. {
  22. row: 'Name',
  23. product1: <Text>{product1.name}</Text>,
  24. product2: <Text>{product2.name}</Text>
  25. },
  26. // More rows...
  27. ...generateOptionalRows()
  28. ];
  29. // good
  30. const rows = [
  31. {
  32. row: 'Name',
  33. product1: <Text>{product1.name}</Text>,
  34. product2: <Text>{product2.name}</Text>
  35. },
  36. {
  37. row: 'Colors',
  38. product1: <ProductOptions options={product1.colors} />,
  39. product2: <ProductOptions options={product2.colors} />,
  40. isVisible: (product1, product2) =>
  41. (product1.colors.length > 0 || product2.colors.length) > 0
  42. },
  43. {
  44. row: 'Sizes',
  45. product1: <ProductOptions options={product1.sizes} />,
  46. product2: <ProductOptions options={product2.sizes} />,
  47. isVisible: (product1, product2) =>
  48. (product1.sizes.length > 0 || product2.sizes.length) > 0
  49. }
  50. ];
  51. const visibleRows = rows.filter(row => {
  52. if (typeof row.isVisible === 'function') {
  53. return row.isVisible(product1, product2);
  54. }
  55. return true;
  56. });

提前返回错误

  1. // bad
  2. function postOrderStatus(orderId) {
  3. var idsArrayObj = getOrderIds();
  4. if (idsArrayObj != undefined) {
  5. if (idsArrayObj.length == undefined) {
  6. var tmpBottle = idsArrayObj;
  7. idsArrayObj = new Array(tmpBottle);
  8. }
  9. var fullRecordsArray = new Array();
  10. if (fullRecordsArray.length != 0)
  11. return sendOrderStatus(fullRecordsArray);
  12. } else {
  13. return false;
  14. }
  15. } else {
  16. return false;
  17. }
  18. }
  1. // 优化后
  2. function postOrderStatus(orderId) {
  3. let idsArrayObj = getOrderIds();
  4. if (idsArrayObj === undefined) {
  5. return false;
  6. }
  7. if (!Array.isArray(idsArrayObj)) {
  8. idsArrayObj = [idsArrayObj];
  9. }
  10. const fullRecordsArray = [];
  11. if (fullRecordsArray.length === 0) {
  12. return false;
  13. }
  14. return sendOrderStatus(fullRecordsArray);
  15. }
  1. // 最终版
  2. function postOrderStatus(orderId) {
  3. const orderIds = getOrderIds(); // 总是返回数组
  4. const fullRecordsArray = [];
  5. if (fullRecordsArray.length === 0) {
  6. return false;
  7. }
  8. return sendOrderStatus(fullRecordsArray);
  9. }

多层if判断优化

  1. // bad
  2. if (month == 'jan') month = 1;
  3. if (month == 'feb') month = 2;
  4. if (month == 'mar') month = 3;
  5. if (month == 'apr') month = 4;
  6. if (month == 'may') month = 5;
  7. if (month == 'jun') month = 6;
  8. if (month == 'jul') month = 7;
  9. if (month == 'aug') month = 8;
  10. if (month == 'sep') month = 9;
  11. if (month == 'oct') month = 10;
  12. if (month == 'nov') month = 11;
  13. if (month == 'dec') month = 12;
  14. // good
  15. const MONTH_NAME_TO_NUMBER = {
  16. jan: 1,
  17. feb: 2,
  18. mar: 3,
  19. apr: 4,
  20. may: 5,
  21. jun: 6,
  22. jul: 7,
  23. aug: 8,
  24. sep: 9,
  25. oct: 10,
  26. nov: 11,
  27. dec: 12
  28. };
  29. const month = MONTH_NAME_TO_NUMBER[monthName];
  1. const DECISION_YES = 0;
  2. const DECISION_NO = 1;
  3. const DECISION_MAYBE = 2;
  4. const getButtonLabel = decisionButton => {
  5. switch (decisionButton) {
  6. case DECISION_YES:
  7. return 'Yes';
  8. case DECISION_NO:
  9. return 'No';
  10. case DECISION_MAYBE:
  11. return 'Maybe';
  12. }
  13. };
  14. <Button>{getButtonLabel(decision.id)}</Button>;
  15. // good
  16. const DECISION_YES = 0;
  17. const DECISION_NO = 1;
  18. const DECISION_MAYBE = 2;
  19. const getButtonLabel = decisionButton =>
  20. ({
  21. [DECISION_YES]: 'Yes',
  22. [DECISION_NO]: 'No',
  23. [DECISION_MAYBE]: 'Maybe'
  24. }[decisionButton]);
  25. <Button>{getButtonLabel(decision.id)}</Button>;
  26. // very good
  27. const DECISION_YES = 0;
  28. const DECISION_NO = 1;
  29. const DECISION_MAYBE = 2;
  30. const ButtonLabel = ({ decision }) =>
  31. ({
  32. [DECISION_YES]: 'Yes',
  33. [DECISION_NO]: 'No',
  34. [DECISION_MAYBE]: 'Maybe'
  35. }[decision]);
  36. <Button>
  37. <ButtonLabel decision={decision.id} />
  38. </Button>;

合理使用三元运算

  1. // bad
  2. let drink;
  3. if (caffeineLevel < 50) {
  4. drink = DRINK_COFFEE;
  5. } else {
  6. drink = DRINK_WATER;
  7. }
  8. // good
  9. const drink = caffeineLevel < 50 ? DRINK_COFFEE : DRINK_WATER;
  1. // bad
  2. function Products({products, isError, isLoading}) {
  3. return isError
  4. ? <p>Error loading products</p>
  5. : isLoading
  6. ? <Loading />
  7. : products.length > 0
  8. ? <ul>{products.map(
  9. product => <li key={product.id}>{product.name}</li>
  10. )}</ul>
  11. : <p>No products found</p>
  12. }
  13. // good
  14. function Products({ products, isError, isLoading }) {
  15. if (isError) {
  16. return <p>Error loading products</p>;
  17. }
  18. if (isLoading) {
  19. return <Loading />;
  20. }
  21. if (products.length === 0) {
  22. return <p>No products found</p>;
  23. }
  24. return (
  25. <ul>
  26. {products.map(product => (
  27. <li key={product.id}>{product.name}</li>
  28. ))}
  29. </ul>
  30. );
  31. }

二、相关实现

new的实现

  1. function newFunc(...args) {
  2. const constructor = args.shift();
  3. const obj = Object.create(constructor.prototype);
  4. const result = constructor.apply(obj, args);
  5. return (typeof result === 'object' && result != null) ? result : obj;
  6. }

call的实现

  1. Function.prototype.call3 = function (context, ...rest) {
  2. context = context ? Object(context) : window;
  3. const key = `_fn${Date.now()}`;
  4. context[key] = this;
  5. const result = context[key](...(rest || []));
  6. delete context[key];
  7. return result;
  8. }
  9. // 测试代码
  10. var value = 2;
  11. var obj = {
  12. value: 1,
  13. }
  14. function bar(name, age) {
  15. console.log(this.value);
  16. return {
  17. value: this.value,
  18. name,
  19. age,
  20. }
  21. }
  22. console.log(bar.call3(null))
  23. console.log(bar.call3(obj, 'kevin', 18));

apply的实现

  1. Function.prototype.apply3 = function (context, rest = []) {
  2. context = context || window;
  3. const key = `fn${Date.now()}`;
  4. console.log(context, rest);
  5. context[key] = this;
  6. const result = context[key](...rest);
  7. delete context[key];
  8. return result;
  9. }
  10. var value = 2;
  11. var obj = {
  12. value: 1,
  13. }
  14. function bar(name, age) {
  15. console.log(this.value);
  16. return {
  17. value: this.value,
  18. name,
  19. age,
  20. }
  21. }
  22. console.log(bar.apply3(null))
  23. console.log(bar.apply3(obj, ['kevin', 18]));

bind的实现方式

  1. Function.prototype.bind2 = function(content) {
  2. if(typeof this != "function") {
  3. throw Error("not a function")
  4. }
  5. // 若没问参数类型则从这开始写
  6. let fn = this;
  7. let args = [...arguments].slice(1);
  8. let resFn = function() {
  9. return fn.apply(this instanceof resFn ? this : content,args.concat(...arguments) )
  10. }
  11. function tmp() {}
  12. tmp.prototype = this.prototype;
  13. resFn.prototype = new tmp();
  14. return resFn;
  15. }

bind导致的内存泄漏

  1. /*
  2. * bind为null或undefined时,foo里面的this指向window,会在window上挂载a变量
  3. */
  4. function foo(a, b) {
  5. this.a = 'test';
  6. console.log( "a:" + a + ",b:" + b );
  7. }
  8. var bar = foo.bind( null, 2 );
  9. bar( 3 ); // a:2,b:3
  10. /*
  11. * 解决方法,使用Object.create, 0, false
  12. */
  13. function foo(a, b) {
  14. console.log( "a:" + a + ",b:" + b );
  15. }
  16. // 我们的空对象
  17. var ø = Object.create( null );
  18. // 使用bind(..)进行柯里化
  19. var bar = foo.bind( ø, 2 );
  20. bar( 3 ); // a:2,b:3

箭头函数的bind、call、apply没有用

  1. var fun = (() => { console.log(this);}).bind({name: 12})
  2. console.log(fun()); //window
  3. // 练习
  4. let result = (function() {
  5. return [
  6. (() => this.x).bind({x: 'inner'})(),
  7. (() => this.x)()
  8. ]
  9. }).call({x: 'outer'})
  10. console.log(result) // [ 'outer', 'outer' ]

使用bind实现偏函数

解释:使一个函数拥有预设的初始参数;

  1. function list() {
  2. return Array.prototype.slice.call(arguments);
  3. }
  4. function addArguments(arg1, arg2) {
  5. return arg1 + arg2
  6. }
  7. var list1 = list(1, 2, 3); // [1, 2, 3]
  8. var result1 = addArguments(1, 2); // 3
  9. // 创建一个函数,它拥有预设参数列表。
  10. var leadingThirtysevenList = list.bind(null, 37);
  11. // 创建一个函数,它拥有预设的第一个参数
  12. var addThirtySeven = addArguments.bind(null, 37);
  13. var list2 = leadingThirtysevenList();
  14. // [37]
  15. var list3 = leadingThirtysevenList(1, 2, 3);
  16. // [37, 1, 2, 3]
  17. var result2 = addThirtySeven(5);
  18. // 37 + 5 = 42
  19. var result3 = addThirtySeven(5, 10);
  20. // 37 + 5 = 42 ,第二个参数被忽略

bind和apply、call之间的异同

相同点:

  1. apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
  2. apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
  3. apply 、 call 、bind 三者都可以利用后续参数传参;

不同的:

  1. bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。

注意点:在Javascript中,多次 bind() 是无效的。

promise实现

  1. // 先定义三个常量表示状态
  2. const PENDING = "pending";
  3. const FULFILLED = "fulfilled";
  4. const REJECTED = "rejected";
  5. // 新建 MyPromise 类
  6. class MyPromise {
  7. constructor(executor) {
  8. // executor 是一个执行器,进入会立即执行
  9. // 并传入resolve和reject方法
  10. try {
  11. executor(this.resolve, this.reject);
  12. } catch (error) {
  13. this.reject(error);
  14. }
  15. }
  16. // 储存状态的变量,初始值是 pending
  17. status = PENDING;
  18. // resolve和reject为什么要用箭头函数?
  19. // 如果直接调用的话,普通函数this指向的是window或者undefined
  20. // 用箭头函数就可以让this指向当前实例对象
  21. // 成功之后的值
  22. value = null;
  23. // 失败之后的原因
  24. reason = null;
  25. // 存储成功回调函数
  26. onFulfilledCallbacks = [];
  27. // 存储失败回调函数
  28. onRejectedCallbacks = [];
  29. // 更改成功后的状态
  30. resolve = (value) => {
  31. // 只有状态是等待,才执行状态修改
  32. if (this.status === PENDING) {
  33. // 状态修改为成功
  34. this.status = FULFILLED;
  35. // 保存成功之后的值
  36. this.value = value;
  37. // resolve里面将所有成功的回调拿出来执行
  38. while (this.onFulfilledCallbacks.length) {
  39. // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
  40. this.onFulfilledCallbacks.shift()(value);
  41. }
  42. }
  43. };
  44. // 更改失败后的状态
  45. reject = (reason) => {
  46. // 只有状态是等待,才执行状态修改
  47. if (this.status === PENDING) {
  48. // 状态成功为失败
  49. this.status = REJECTED;
  50. // 保存失败后的原因
  51. this.reason = reason;
  52. // resolve里面将所有失败的回调拿出来执行
  53. while (this.onRejectedCallbacks.length) {
  54. this.onRejectedCallbacks.shift()(reason);
  55. }
  56. }
  57. };
  58. then(onFulfilled, onRejected) {
  59. const realOnFulfilled =
  60. typeof onFulfilled === "function" ? onFulfilled : (value) => value;
  61. const realOnRejected =
  62. typeof onRejected === "function"
  63. ? onRejected
  64. : (reason) => {
  65. throw reason;
  66. };
  67. // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
  68. const promise2 = new MyPromise((resolve, reject) => {
  69. const fulfilledMicrotask = () => {
  70. // 创建一个微任务等待 promise2 完成初始化
  71. queueMicrotask(() => {
  72. try {
  73. // 获取成功回调函数的执行结果
  74. const x = realOnFulfilled(this.value);
  75. // 传入 resolvePromise 集中处理
  76. resolvePromise(promise2, x, resolve, reject);
  77. } catch (error) {
  78. reject(error);
  79. }
  80. });
  81. };
  82. const rejectedMicrotask = () => {
  83. // 创建一个微任务等待 promise2 完成初始化
  84. queueMicrotask(() => {
  85. try {
  86. // 调用失败回调,并且把原因返回
  87. const x = realOnRejected(this.reason);
  88. // 传入 resolvePromise 集中处理
  89. resolvePromise(promise2, x, resolve, reject);
  90. } catch (error) {
  91. reject(error);
  92. }
  93. });
  94. };
  95. // 判断状态
  96. if (this.status === FULFILLED) {
  97. fulfilledMicrotask();
  98. } else if (this.status === REJECTED) {
  99. rejectedMicrotask();
  100. } else if (this.status === PENDING) {
  101. // 等待
  102. // 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
  103. // 等到执行成功失败函数的时候再传递
  104. this.onFulfilledCallbacks.push(fulfilledMicrotask);
  105. this.onRejectedCallbacks.push(rejectedMicrotask);
  106. }
  107. });
  108. return promise2;
  109. }
  110. catch(onRejected) {
  111. // 只需要进行错误处理
  112. this.then(undefined, onRejected);
  113. }
  114. finally(fn) {
  115. return this.then(
  116. (value) => {
  117. return MyPromise.resolve(fn()).then(() => {
  118. return value;
  119. });
  120. },
  121. (error) => {
  122. return MyPromise.resolve(fn()).then(() => {
  123. throw error;
  124. });
  125. }
  126. );
  127. }
  128. // resolve 静态方法
  129. static resolve(parameter) {
  130. // 如果传入 MyPromise 就直接返回
  131. if (parameter instanceof MyPromise) {
  132. return parameter;
  133. }
  134. // 转成常规方式
  135. return new MyPromise((resolve) => {
  136. resolve(parameter);
  137. });
  138. }
  139. // reject 静态方法
  140. static reject(reason) {
  141. return new MyPromise((resolve, reject) => {
  142. reject(reason);
  143. });
  144. }
  145. static all(promiseList) {
  146. return new MyPromise((resolve, reject) => {
  147. const result = [];
  148. const length = promiseList.length;
  149. let count = 0;
  150. if (length === 0) {
  151. return resolve(result);
  152. }
  153. promiseList.forEach((promise, index) => {
  154. MyPromise.resolve(promise).then(
  155. (value) => {
  156. count++;
  157. result[index] = value;
  158. if (count === length) {
  159. resolve(result);
  160. }
  161. },
  162. (reason) => {
  163. reject(reason);
  164. }
  165. );
  166. });
  167. });
  168. }
  169. static allSettled = (promiseList) => {
  170. return new MyPromise((resolve) => {
  171. const length = promiseList.length;
  172. const result = [];
  173. let count = 0;
  174. if (length === 0) {
  175. return resolve(result);
  176. } else {
  177. for (let i = 0; i < length; i++) {
  178. const currentPromise = MyPromise.resolve(promiseList[i]);
  179. currentPromise.then(
  180. (value) => {
  181. count++;
  182. result[i] = {
  183. status: "fulfilled",
  184. value: value,
  185. };
  186. if (count === length) {
  187. return resolve(result);
  188. }
  189. },
  190. (reason) => {
  191. count++;
  192. result[i] = {
  193. status: "rejected",
  194. reason: reason,
  195. };
  196. if (count === length) {
  197. return resolve(result);
  198. }
  199. }
  200. );
  201. }
  202. }
  203. });
  204. };
  205. static race(promiseList) {
  206. return new MyPromise((resolve, reject) => {
  207. const length = promiseList.length;
  208. if (length === 0) {
  209. return resolve();
  210. } else {
  211. for (let i = 0; i < length; i++) {
  212. MyPromise.resolve(promiseList[i]).then(
  213. (value) => {
  214. return resolve(value);
  215. },
  216. (reason) => {
  217. return reject(reason);
  218. }
  219. );
  220. }
  221. }
  222. });
  223. }
  224. }
  225. function resolvePromise(promise, x, resolve, reject) {
  226. // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
  227. // 这是为了防止死循环
  228. if (promise === x) {
  229. return reject(
  230. new TypeError("The promise and the return value are the same")
  231. );
  232. }
  233. if (typeof x === "object" || typeof x === "function") {
  234. // 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
  235. if (x === null) {
  236. return resolve(x);
  237. }
  238. let then;
  239. try {
  240. // 把 x.then 赋值给 then
  241. then = x.then;
  242. } catch (error) {
  243. // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
  244. return reject(error);
  245. }
  246. // 如果 then 是函数
  247. if (typeof then === "function") {
  248. let called = false;
  249. // 将 x 作为函数的作用域 this 调用之
  250. // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
  251. // 名字重名了,我直接用匿名函数了
  252. try {
  253. then.call(
  254. x,
  255. // 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
  256. (y) => {
  257. // 如果 resolvePromise 和 rejectPromise 均被调用,
  258. // 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
  259. // 实现这条需要前面加一个变量called
  260. if (called) return;
  261. called = true;
  262. resolvePromise(promise, y, resolve, reject);
  263. },
  264. // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
  265. (r) => {
  266. if (called) return;
  267. called = true;
  268. reject(r);
  269. }
  270. );
  271. } catch (error) {
  272. // 如果调用 then 方法抛出了异常 e:
  273. // 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
  274. if (called) return;
  275. // 否则以 e 为据因拒绝 promise
  276. reject(error);
  277. }
  278. } else {
  279. // 如果 then 不是函数,以 x 为参数执行 promise
  280. resolve(x);
  281. }
  282. } else {
  283. // 如果 x 不为对象或者函数,以 x 为参数执行 promise
  284. resolve(x);
  285. }
  286. }
  287. MyPromise.deferred = function () {
  288. var result = {};
  289. result.promise = new MyPromise(function (resolve, reject) {
  290. result.resolve = resolve;
  291. result.reject = reject;
  292. });
  293. return result;
  294. };
  295. module.exports = MyPromise;
  1. class MyPromise {
  2. constructor (fn) {
  3. this.fn = fn;
  4. this.resolve = v => v;
  5. const _resolve = (v) => {
  6. this.status = 'resolve'
  7. this.resolve(v)
  8. this.res = v
  9. }
  10. this.fn(_resolve)
  11. }
  12. then(cb) {
  13. const wrapperCb = val => {
  14. if (val.constructor === MyPromise) {
  15. return val.then(cb)
  16. } else {
  17. return cb(val)
  18. }
  19. }
  20. const resolve = this.resolve
  21. this.resolve = (...args) => wrapperCb(resolve(...args))
  22. // 检测状态 未完成就挂起 完成就执行
  23. if(this.status === 'resolve') {
  24. this.resolve(this.res)
  25. }
  26. return this
  27. }
  28. }
  29. var data = new MyPromise((res) => {
  30. setTimeout(() => {
  31. console.log('start');
  32. res('start')
  33. }, 2000)
  34. // res('start')
  35. })
  36. data.then((res) => {
  37. // return res + 44
  38. console.log('res1');
  39. return new MyPromise((res2) => setTimeout(() => res2(res + '11'), 1000));
  40. // return new MyPromise((res2) => res2(res + '11'));
  41. }).then((res) => {
  42. // return res + 44
  43. console.log('res2');
  44. // return new MyPromise((res2) => setTimeout(() => res2(res + '11'), 1000));
  45. return new MyPromise((res2) => res2(res + '22'));
  46. }).then(console.log);

深拷贝

  1. 方式一 ```javascript function getEmpty(o) { const types = {
    1. '[object Object]': {},
    2. '[object Array]': [],
    } const type = Object.prototype.toString.call(o); if(types[type]) {
    1. return types[type]
    } return o; }

function deepClone(origin) { const queue = []; const map = new Map(); const target = getEmpty(origin); if(target !== origin) { queue.push([origin, target]); map.set(origin, target); } while(queue.length) { const [ori, tar] = queue.shift(); for(let key in ori) { if(map.get(ori[key])){ tar[key] = map.get(ori[key]); continue; } tar[key] = getEmpty(ori[key]); if(tar[key] !== ori[key]){ queue.push([ori[key], tar[key]]); map.set(ori[key], tar[key]); } } } return target; }

// 测试代码 const obj = { name: 1, range: [17, 30], getName: () => { console.log(this.name); } } const newObj = deepClone(obj); newObj.name = ‘333’ newObj.range.push(66) newObj.test = 18 console.log(obj, newObj);

  1. 2. 方式二
  2. ```javascript
  3. function deepClone(obj) {
  4. if(obj == null || typeof obj !== 'object') return obj
  5. // 时间对象有特殊属性
  6. const newObj = new obj.constructor(obj.constructor === Date ? obj : null)
  7. // 不拷贝原型链的
  8. for (const key in Object.getOwnPropertyDescriptors(obj)) {
  9. newObj[key] = deepClone(obj[key])
  10. }
  11. return newObj
  12. }

三、函数式编程

函数柯里化

小记 · 语雀

once实现

  1. const once = () => {
  2. let done = false;
  3. return function(){
  4. return done?undefined: ((done = true), fn.apply(this,arguments))
  5. }
  6. }

memoized实现

  1. const memoized = () => {
  2. const lookupTable = {};
  3. return (arg) => lookupTable[arg] || (lookupTable[arg] = fn(arg))
  4. }

unary实现

  1. // 让函数只接受一个参数
  2. const unary = (fn) => fn.length === 1 ? fn: (arg) => fn(arg)

四、相关应用

switch里面case和default一起使用

  1. switch(step){
  2. case 1 : default: {
  3. html='<h1></h1>'; break;
  4. }
  5. case 2:{
  6. html = '<h2></h2>' ; break;
  7. }
  8. case 3:{
  9. html = '<h3></h3>'; break;
  10. }
  11. }

小数求最大值、最小值

  1. const readings = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4];
  2. const maxReading = readings.reduce((x, y) => Math.max(x, y), Number.MIN_VALUE);
  3. const minReading = readings.reduce((x, y) => Math.min(x, y), Number.MAX_VALUE);
  4. console.log({minReading, maxReading});
  5. // ⦘ {minReading: 0.2, maxReading: 5.5}

点击区域以外

  1. const handler = event => {
  2. const { current: el } = ref;
  3. el && !el.contains(event.target) && onClickAway(event);
  4. };
  5. document.body.addEventListener('click', e => {
  6. if (e.target && e.target.matches('div.code')) {
  7. return;
  8. }
  9. // do sthing
  10. });

判断函数参数的个数

  1. var a = function(a){}
  2. console.log(a.lenth); // 1
  3. var b = function(a,b){}
  4. console.log(b.lenth); // 2
  5. // 让函数只接受一个参数
  6. const unary = () => fn.length === 1 ? fn : (arg) => fn(arg)

发布订阅模式

  1. class Event{
  2. constructor(){
  3. this.callbacks = {}
  4. }
  5. $off(name){
  6. this.callbacks[name] = null
  7. }
  8. $emit(name, args){
  9. let cbs = this.callbacks[name]
  10. if (cbs) {
  11. cbs.forEach(c=>{
  12. c.call(this, args)
  13. })
  14. }
  15. }
  16. $on(name, fn){
  17. (this.callbacks[name] || (this.callbacks[name] = [])).push(fn)
  18. }
  19. }
  20. let event = new Event()
  21. event.$on('event1', function(arg){
  22. console.log('事件1',arg)
  23. })
  24. event.$emit('event1',{name:'测试'})

axios

  1. const axios = require('axios');
  2. const instance = axios.create({baseURL: 'http://localhost:3000/api'});
  3. const METHODS = ['get', 'post', 'patch'];
  4. const api = new Proxy({}, {
  5. get: (_, name) => new Proxy({}, {
  6. get:(_, method) => METHODS.includes(method) && new Proxy(() => {}, {
  7. apply:(_, self, [config]) => instance.request({
  8. url: name,
  9. method,
  10. ...config,
  11. })
  12. })
  13. })
  14. })
  15. api.user.get({params: {id: 12}}).then((user) => console.log(user)).catch(console.error)

装饰器

  1. @testDec
  2. class Demo {
  3. // ...
  4. }
  5. function testDec(targe){
  6. target.isDec = true;
  7. }
  8. alert(Demo.isDec) // true

js异步校验

  1. var fns = [
  2. v => v.length > 1,
  3. async (v) => new Promise((res, rej) => setTimeout(() => {
  4. if(v.length <= 2) {
  5. rej('小于2')
  6. } else {
  7. res('ok')
  8. }
  9. }, 1000)),
  10. v => v.length > 5,
  11. ];
  12. async function validate(val, fns) {
  13. let res;
  14. for (let i = 0; i < fns.length; i++) {
  15. const fn = fns[i];
  16. if(i === 0) {
  17. res = fn(val) ? Promise.resolve(fn(val)) : Promise.reject(fn(val))
  18. } else {
  19. res = res.then(() => fn(val))
  20. }
  21. }
  22. return res;
  23. }
  24. validate('134', fns).then(res => {
  25. console.log('then', res);
  26. }).catch(err => {
  27. console.log('catch', err);
  28. })

五、相关工具函数

  1. /** 数据格式化 **/
  2. function formatSize(size: number): string {
  3. let subFix: number = 0;
  4. let temp: number = size;
  5. let fixArry: string[] = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
  6. while (temp >= 1024) {
  7. if (subFix > fixArry.length - 1) {
  8. break;
  9. }
  10. temp = temp / 1024;
  11. subFix++;
  12. }
  13. let fixExt: string = fixArry[subFix];
  14. let stringSize: string = temp.toFixed(2) + fixExt;
  15. return stringSize;
  16. }
  17. function bytesToSize (bytes, point = 1) {
  18. if (bytes === 0) {
  19. return '0 B'
  20. }
  21. const k = 1024 // or 1024
  22. const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
  23. const i = Math.floor(Math.log(bytes) / Math.log(k))
  24. const val = bytes / Math.pow(k, i)
  25. let size = val.toPrecision(3)
  26. size = val.toFixed(point)
  27. return size + sizes[i]
  28. }
  29. function secondToDate (seconds) {
  30. const h = Math.floor(seconds / 3600).toString()
  31. const m = Math.floor((seconds / 60 % 60)).toString()
  32. const s = Math.floor((seconds % 60)).toString()
  33. return `${h.padStart(2, '0')}:${m.padStart(2, '0')}:${s.padStart(2, '0')}`
  34. }
  35. function fullscreen () {
  36. const ele = document.documentElement
  37. if (ele.requestFullscreen) {
  38. ele.requestFullscreen()
  39. } else if (ele.mozRequestFullScreen) {
  40. ele.mozRequestFullScreen()
  41. } else if (ele.webkitRequestFullScreen) {
  42. ele.webkitRequestFullScreen()
  43. setTimeout(() => {
  44. console.error('fullscreen success')
  45. document.dispatchEvent(new Event('fullscreenchange'))
  46. }, 0)
  47. }
  48. }
  49. /** 时间格式化 **/
  50. function parseTime (time, cFormat) {
  51. if (arguments.length === 0) {
  52. return null
  53. }
  54. const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
  55. let date
  56. if (typeof time === 'object') {
  57. date = time
  58. } else {
  59. if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
  60. time = parseInt(time)
  61. }
  62. if ((typeof time === 'number') && (time.toString().length === 10)) {
  63. time = time * 1000
  64. }
  65. date = new Date(time)
  66. }
  67. const formatObj = {
  68. y: date.getFullYear(),
  69. m: date.getMonth() + 1,
  70. d: date.getDate(),
  71. h: date.getHours(),
  72. i: date.getMinutes(),
  73. s: date.getSeconds(),
  74. a: date.getDay()
  75. }
  76. const timeStr = format.replace(/{([ymdhisa])+}/g, (result, key) => {
  77. const value = formatObj[key]
  78. // Note: getDay() returns 0 on Sunday
  79. if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
  80. return value.toString().padStart(2, '0')
  81. })
  82. return timeStr
  83. }
  84. /** url参数解析 **/
  85. function getSearchParams () {
  86. const querySearch = location.search.slice(1)
  87. const searchMap = {}
  88. querySearch
  89. .split('&')
  90. .map(item => item.split('='))
  91. .map(item => searchMap[item[0]] = item[1])
  92. return searchMap.from
  93. }
  1. import * as Comlink from 'comlink'
  2. import CryptoJS from 'crypto-js'
  3. class Gcid {
  4. constructor () {
  5. this.gcid = ''
  6. this.gcidSHA1 = ''
  7. }
  8. create () {
  9. this.gcid = ''
  10. this.gcidSHA1 = CryptoJS.algo.SHA1.create()
  11. }
  12. calculate (ab, blockSize) {
  13. const size = ab.byteLength
  14. const blockNum = size / blockSize
  15. for (let i = 0; i < blockNum; i++) {
  16. const wa = CryptoJS.lib.WordArray.create(ab.slice(blockSize * i, blockSize * (i + 1)))
  17. const bcidSHA1 = CryptoJS.SHA1(wa)
  18. this.gcidSHA1.update(bcidSHA1)
  19. }
  20. if (blockSize * blockNum < size) {
  21. const wa = CryptoJS.lib.WordArray.create(ab.slice(blockSize * blockNum, size))
  22. const bcidSHA1 = CryptoJS.SHA1(wa)
  23. this.gcidSHA1.update(bcidSHA1)
  24. }
  25. }
  26. finalize () {
  27. this.gcid = this.gcidSHA1.finalize().toString().toUpperCase()
  28. console.log('worker计算出来的gcid', this.gcid)
  29. }
  30. }
  31. Comlink.expose(Gcid)
  1. import store from 'store'
  2. import storeExpire from 'store/plugins/expire'
  3. import OSS from 'ali-oss'
  4. import * as Comlink from 'comlink'
  5. import { createFile, saveOSSFile } from '@/api/drive'
  6. import { getCookie } from '@/utils/util'
  7. import constants from '@/utils/constants'
  8. // ali-oss 文档
  9. // https://help.aliyun.com/document_detail/64047.html?spm=a2c4g.11186623.6.1328.155479f8sfeFfZ#title-vbx-3di-xnh
  10. // let gcidWorker = null
  11. store.addPlugin(storeExpire)
  12. function calculateBlockSize (size) {
  13. if (size >= 0 && size <= (128 << 20)) {
  14. return 256 << 10
  15. }
  16. if (size > (128 << 20) && size <= (256 << 20)) {
  17. return 512 << 10
  18. }
  19. if (size > (256 << 20) && size <= (512 << 20)) {
  20. return 1024 << 10
  21. }
  22. return 2048 << 10
  23. }
  24. export async function readFileGcid (file, cb) {
  25. console.log('create gcidWorker start', new Date().getTime())
  26. let gcidWorker = null
  27. if (!gcidWorker) {
  28. const MyWorker = Comlink.wrap(
  29. new Worker(`${location.origin}/gcidWorker.js`)
  30. )
  31. gcidWorker = await new MyWorker()
  32. }
  33. await gcidWorker.create()
  34. // console.log('create gcidWorker end', new Date().getTime())
  35. return new Promise((resolve, reject) => {
  36. // console.log('create fileReader start', new Date().getTime())
  37. const reader = new window.FileReader() // FileReader实例
  38. // console.log('create fileReader end', new Date().getTime())
  39. // console.log('create blocksize start', new Date().getTime())
  40. const blockSize = calculateBlockSize(file.size)
  41. // console.log('create blocksize end', new Date().getTime())
  42. const CHUNK_MIN_SIZE = 100 * 1024 * 1024
  43. const CHUNK_SIZE = Math.floor(CHUNK_MIN_SIZE / blockSize) * blockSize // 以blockSize为单位做最大分片(小于100M)
  44. const CHUNK_LEN = Math.ceil(file.size / CHUNK_SIZE)
  45. let reverse_index = 0
  46. // console.log(`每${CHUNK_SIZE}大小分片读取文件中~~`, CHUNK_LEN)
  47. reader.onload = async () => {
  48. // console.log('onload')
  49. let calStart = new Date().getTime()
  50. // console.log('create calculate start', calStart)
  51. // console.log(reader.result)
  52. // console.log(blockSize)
  53. await gcidWorker.calculate(reader.result, blockSize)
  54. let calend = new Date().getTime()
  55. // console.log('create calculate end', calend)
  56. // console.log("cal cost :", calend - calStart)
  57. reverse_index += 1
  58. let ret = cb()
  59. if (ret === constants.UPLOAD_PAUSE) {
  60. return resolve(constants.UPLOAD_PAUSE)
  61. }
  62. // return resolve(constants.UPLOAD_PAUSE)
  63. // progress((10 * reverse_index / CHUNK_LEN).toFixed(2))
  64. // 删除情况
  65. // if (!file) {
  66. // return resolve(null)
  67. // }
  68. // 通过 file status 管理文件任务状态
  69. // if (!checkUploadTask(file.name)) {
  70. // progress(-2)
  71. // return resolve(null)
  72. // }
  73. if (reverse_index >= CHUNK_LEN) {
  74. // console.log('create calculate finalize start', new Date().getTime())
  75. await gcidWorker.finalize()
  76. // console.log('create calculate finalize end', new Date().getTime())
  77. const gcid = await gcidWorker.gcid
  78. gcidWorker = null
  79. resolve(gcid)
  80. } else {
  81. seek()
  82. }
  83. }
  84. seek();
  85. function seek() {
  86. let start = CHUNK_SIZE * reverse_index;
  87. let end = (reverse_index >= CHUNK_LEN - 1) ? file.size : CHUNK_SIZE * (reverse_index + 1);
  88. let slice = file.slice(start, end);
  89. reader.readAsArrayBuffer(slice);
  90. }
  91. })
  92. }
  93. async function promiseCreateFile (params) {
  94. return new Promise(async (resolve) => {
  95. try {
  96. const result = await createFile(params)
  97. resolve([null, result])
  98. } catch(err) {
  99. resolve([err, null])
  100. }
  101. })
  102. }
  103. // 云添加使用的Url上传
  104. async function uploadUrl ({
  105. name = '',
  106. size = 0,
  107. url,
  108. files = [],
  109. parentId = '',
  110. kind = 'drive#file',
  111. unionId = '',
  112. require_links = false
  113. }) {
  114. let hash = ''
  115. const createParmas = {
  116. upload_type: 'UPLOAD_TYPE_URL',
  117. kind,
  118. parent_id: parentId,
  119. name,
  120. hash,
  121. size,
  122. url: {
  123. url,
  124. files
  125. },
  126. unionId,
  127. // 创建文件请求中, 增加"params": { "require_links": "true" } ,表示需要直接返回播放链接(边添加边播模式)
  128. params: {
  129. require_links: String(require_links)
  130. }
  131. }
  132. const [err, fileData] = await promiseCreateFile(createParmas)
  133. if (err) return err
  134. return fileData
  135. }
  136. // 使用URL进行云添加
  137. async function uploadUrlV2 ({
  138. name = '',
  139. size = 0,
  140. url,
  141. files = [],
  142. parentId = '',
  143. kind = 'drive#file',
  144. unionId = '',
  145. require_links = false
  146. }) {
  147. let hash = ''
  148. const createParams = {
  149. upload_type: 'UPLOAD_TYPE_URL',
  150. kind,
  151. parent_id: parentId,
  152. name,
  153. hash,
  154. size,
  155. url: {
  156. url,
  157. files
  158. },
  159. unionId,
  160. // 创建文件请求中, 增加"params": { "require_links": "true" } ,表示需要直接返回播放链接(边添加边播模式)
  161. params: {
  162. require_links: String(require_links)
  163. }
  164. }
  165. return promiseCreateFile(createParams);
  166. }
  167. // 统一上传接口
  168. async function uploadFile ({
  169. file,
  170. maxSize = 5,
  171. type = 'UPLOAD_TYPE_FORM',
  172. parentId = '',
  173. kind = 'drive#file',
  174. gcid,
  175. fileId,
  176. xlUploadCache,
  177. progress = (p) => null,
  178. createCb
  179. }) {
  180. console.log('uploadfile')
  181. let uploadType = type
  182. // 临时file.id,用于处理还在计算gcid阶段,删除操作. id 使用时间戳
  183. // createCb && createCb(Object.assign({}, file, {
  184. // id: new Date().getTime()
  185. // }))
  186. // if (!xlUploadCache) {
  187. // // 没有缓存时才重新计算gcid
  188. // console.log('得到的gcid', gcid)
  189. // }
  190. // console.log('文件名: --------')
  191. // console.log('xlUploadCache: ' ,xlUploadCache)
  192. // console.log(file.name)
  193. // return gcid
  194. const createParmas = {
  195. kind,
  196. parent_id: parentId,
  197. name: file.name,
  198. hash: gcid,
  199. size: file.size
  200. }
  201. if (file.size > maxSize * 1024 * 1024) {
  202. // 大文件使用断点分片上传
  203. uploadType = 'UPLOAD_TYPE_RESUMABLE'
  204. }
  205. // TODO 优化 使用 接口返回的进行上传
  206. createParmas.upload_type = uploadType
  207. console.log('uploadfile1')
  208. if (uploadType === 'UPLOAD_TYPE_FORM') {
  209. // 表单上传,返回oss地址
  210. const [err, createRes] = await promiseCreateFile(createParmas)
  211. if (err) {
  212. console.log(err)
  213. progress(constants.UPLOAD_FAIL)
  214. return
  215. }
  216. createCb && createCb(createRes.file, createRes.upload_type)
  217. if (createRes.file && createRes.file.phase === 'PHASE_TYPE_COMPLETE') {
  218. return createRes.file
  219. }
  220. if (!createRes.form) {
  221. // 如果没有form,可能为秒传
  222. return createRes.file
  223. }
  224. console.log('uploadfile2')
  225. try {
  226. await saveOSSFile(createRes.form.url, {
  227. ...createRes.form.multi_parts,
  228. file
  229. }, {
  230. headers: { 'Content-Type': 'multipart/form-data' },
  231. onUploadProgress: function(progressEvent) {
  232. let percentCompleted = ((progressEvent.loaded * 100) / progressEvent.total).toFixed(0)
  233. let fakeCheckPoint = {
  234. fileSize: progressEvent.total
  235. }
  236. progress(percentCompleted, fakeCheckPoint, null)
  237. }
  238. })
  239. return createRes.file
  240. } catch(err) {
  241. return Promise.resolve(null)
  242. }
  243. } else {
  244. let clientObj = xlUploadCache
  245. const [err, createRes] = await promiseCreateFile(createParmas)
  246. if (err) {
  247. console.log(err)
  248. progress(constants.UPLOAD_FAIL)
  249. return
  250. }
  251. if (!createRes.file) return createRes
  252. if (createRes.file && createRes.file.phase === 'PHASE_TYPE_COMPLETE') {
  253. return createRes.file
  254. }
  255. if (!createRes.resumable) {
  256. // 如果没有resumable,可能为秒传
  257. return createRes.file
  258. }
  259. if (!xlUploadCache) {
  260. // 没有缓存记录,重新创建oss地址
  261. createCb && createCb(createRes.file, createRes.upload_type) // 回调更新文件id,用于删除
  262. if (createRes.upload_type === 'UPLOAD_TYPE_FORM') {
  263. // 服务端不支持分片上传
  264. if (file.size > 100 * 1024 * 1024) return Promise.resolve(null)
  265. const timeout = parseInt(file.size / (200 * 1024))
  266. try {
  267. await saveOSSFile(createRes.form.url, {
  268. ...createRes.form.multi_parts,
  269. file
  270. }, {
  271. headers: { 'Content-Type': 'multipart/form-data' },
  272. timeout: timeout * 1000,
  273. onUploadProgress: function(progressEvent) {
  274. let percentCompleted = ((progressEvent.loaded * 100) / progressEvent.total).toFixed(0)
  275. let fakeCheckPoint = {
  276. fileSize: progressEvent.total
  277. }
  278. progress(percentCompleted, fakeCheckPoint, null)
  279. }
  280. })
  281. return createRes.file
  282. } catch(err) {
  283. return Promise.resolve(null)
  284. }
  285. }
  286. clientObj = createRes.resumable.params
  287. clientObj.file = createRes.file
  288. } else {
  289. createCb && createCb(clientObj.file, 'UPLOAD_TYPE_RESUMABLE') // 回调更新文件id,用于删除
  290. }
  291. let ossParams = {
  292. endpoint: clientObj.endpoint,
  293. accessKeyId: clientObj.access_key_id,
  294. accessKeySecret: clientObj.access_key_secret,
  295. bucket: clientObj.bucket,
  296. stsToken: clientObj.security_token,
  297. secure: true
  298. }
  299. const client = new OSS(ossParams)
  300. // browser 分片上传不需要initMultipartUpload和completeMultipartUpload
  301. // 有效期
  302. const validTime = new Date(clientObj.expiration)
  303. if (xlUploadCache) {
  304. // 缓存时 checkpoint.file 会丢失
  305. clientObj.checkpoint.file = file
  306. }
  307. // 暂停分片上传 client.cancel()
  308. const uploadRes = await client.multipartUpload(clientObj.key, file, {
  309. checkpoint: clientObj.checkpoint || '',
  310. // parallel: 3,
  311. partSize: 3 * 1024 * 1024,
  312. progress: (p, checkpoint) => {
  313. progress((p * 100).toFixed(2), checkpoint, client)
  314. // 缓存断点的记录数据
  315. const saveObj = {
  316. ...clientObj,
  317. fileId,
  318. checkpoint: {
  319. ...checkpoint,
  320. file: null
  321. }
  322. }
  323. store.set(`xlUploadCache-${saveObj.fileId}`, saveObj, new Date(validTime).getTime())
  324. // if (!checkUploadTask(file.name)) {
  325. // client.cancel()
  326. // }
  327. }
  328. }).then(res => {
  329. delUploadStore(fileId)
  330. return res
  331. }).catch(err => {
  332. progress(constants.UPLOAD_FAIL)
  333. // if (!checkUploadTask(file.name)) {
  334. // progress(-2)
  335. // } else {
  336. // if (err.status !== 0) {
  337. // progress(constants.UPLOAD_FAIL)
  338. // } else {
  339. // progress(constants.UPLOAD_PAUSE)
  340. // }
  341. // }
  342. })
  343. if (uploadRes && uploadRes.name) {
  344. return clientObj.file
  345. }
  346. }
  347. }
  348. export async function checkUpload (fileId) {
  349. // 检测断点上传的数据
  350. let xlUploadCache = store.get(`xlUploadCache-${fileId}`)
  351. if (xlUploadCache) {
  352. if (xlUploadCache.fileId === fileId) {
  353. return xlUploadCache
  354. }
  355. }
  356. return null
  357. }
  358. export async function delUploadStore (fileId) {
  359. store.remove(`xlUploadCache-${fileId}`)
  360. }
  361. function checkUploadTask (name) {
  362. // 检测当前上传任务是否存在
  363. // 或者可使用window全局变量来控制
  364. let val = getCookie('xlUploadTask')
  365. val = val ? decodeURIComponent(val) : null
  366. if (val === name) return true
  367. return false
  368. }
  369. /*
  370. * 新上传接口,支持多任务同时上传,支持同一时刻上传任务数量限制
  371. * @params
  372. */
  373. async function uploadFileV2 ({
  374. file,
  375. maxSize = 5,
  376. type = 'UPLOAD_TYPE_FORM',
  377. parentId = '',
  378. kind = 'drive#file',
  379. gcid,
  380. fileId,
  381. xlUploadCache,
  382. progress = (p) => null,
  383. createCb
  384. }) {
  385. let uploadType = type
  386. const createParmas = {
  387. kind,
  388. parent_id: parentId === '*' ? '' : parentId,
  389. name: file.name,
  390. hash: gcid,
  391. size: file.size
  392. }
  393. if (file.size > maxSize * 1024 * 1024) {
  394. // 大文件使用断点分片上传
  395. uploadType = 'UPLOAD_TYPE_RESUMABLE'
  396. }
  397. // TODO 优化 使用 接口返回的进行上传
  398. createParmas.upload_type = uploadType
  399. const [err, createRes] = await promiseCreateFile(createParmas)
  400. if (err) {
  401. console.log(err)
  402. // progress(constants.UPLOAD_FAIL)
  403. createCb && createCb(null, null, err)
  404. return
  405. }
  406. createCb && createCb(createRes.file, createRes.upload_type)
  407. if (createRes.file && createRes.file.phase === 'PHASE_TYPE_COMPLETE') {
  408. return createRes.file
  409. }
  410. if (createRes.upload_type === constants.UPLOAD_TYPE_FORM) {
  411. if (!createRes.form) {
  412. // 如果没有form,可能为秒传
  413. return createRes.file
  414. }
  415. // OSS上传
  416. try {
  417. await saveOSSFile(createRes.form.url, {
  418. ...createRes.form.multi_parts,
  419. file
  420. }, {
  421. headers: { 'Content-Type': 'multipart/form-data' },
  422. onUploadProgress: function(progressEvent) {
  423. let percentCompleted = ((progressEvent.loaded * 100) / progressEvent.total).toFixed(0)
  424. let fakeCheckPoint = {
  425. fileSize: progressEvent.total
  426. }
  427. progress(percentCompleted, fakeCheckPoint, null)
  428. }
  429. })
  430. return createRes.file
  431. } catch(err) {
  432. return Promise.resolve(null)
  433. }
  434. } else if (createRes.upload_type === constants.UPLOAD_TYPE_UNKNOWN) {
  435. if (createRes.file && createRes.file.phase === 'PHASE_TYPE_COMPLETE') {
  436. return createRes.file
  437. }
  438. } else {
  439. let clientObj = xlUploadCache
  440. if (!xlUploadCache) {
  441. clientObj = createRes.resumable.params
  442. clientObj.file = createRes.file
  443. }
  444. // 分片上传
  445. let ossParams = {
  446. endpoint: clientObj.endpoint,
  447. accessKeyId: clientObj.access_key_id,
  448. accessKeySecret: clientObj.access_key_secret,
  449. bucket: clientObj.bucket,
  450. stsToken: clientObj.security_token,
  451. secure: true
  452. }
  453. const client = new OSS(ossParams)
  454. // browser 分片上传不需要initMultipartUpload和completeMultipartUpload
  455. // 有效期
  456. const validTime = new Date(clientObj.expiration)
  457. if (xlUploadCache) {
  458. // 缓存时 checkpoint.file 会丢失
  459. clientObj.checkpoint.file = file
  460. }
  461. // 暂停分片上传 client.cancel()
  462. const uploadRes = await client.multipartUpload(clientObj.key, file, {
  463. checkpoint: clientObj.checkpoint || '',
  464. // parallel: 3,
  465. partSize: 3 * 1024 * 1024,
  466. progress: (p, checkpoint) => {
  467. progress((p * 100).toFixed(2), checkpoint, client)
  468. // 缓存断点的记录数据
  469. const saveObj = {
  470. ...clientObj,
  471. fileId,
  472. checkpoint: {
  473. ...checkpoint,
  474. file: null
  475. }
  476. }
  477. store.set(`xlUploadCache-${saveObj.fileId}`, saveObj, new Date(validTime).getTime())
  478. }
  479. }).then(res => {
  480. delUploadStore(fileId)
  481. return res
  482. }).catch(err => {
  483. return Promise.resolve(null)
  484. })
  485. if (uploadRes && uploadRes.name) {
  486. return clientObj.file
  487. }
  488. }
  489. }
  490. export default {
  491. uploadUrl,
  492. uploadUrlV2,
  493. uploadFile,
  494. uploadFileV2,
  495. checkUploadTask,
  496. checkUpload,
  497. delUploadStore,
  498. readFileGcid
  499. }

滑块实现

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta
  6. name="viewport"
  7. content="width=device-width, initial-scale=1, maximum-scale=1"
  8. />
  9. <title>滑块</title>
  10. <style>
  11. body {
  12. padding: 100px 0;
  13. }
  14. .xl-container {
  15. padding: 20px;
  16. }
  17. /** 滑块 **/
  18. .xl-slider {
  19. height: 4px;
  20. background: #eee;
  21. border-radius: 3px;
  22. position: relative;
  23. cursor: pointer;
  24. }
  25. .xl-slider-bar {
  26. border-radius: 3px;
  27. position: absolute;
  28. height: 100%;
  29. }
  30. .xl-slider-step {
  31. position: absolute;
  32. top: 0;
  33. width: 4px;
  34. height: 4px;
  35. border-radius: 50%;
  36. background: #fff;
  37. -webkit-transform: translateX(-50%);
  38. transform: translateX(-50%);
  39. }
  40. .xl-slider-wrap {
  41. width: 36px;
  42. height: 36px;
  43. position: absolute;
  44. top: -16px;
  45. -webkit-transform: translateX(-50%);
  46. transform: translateX(-50%);
  47. z-index: 10;
  48. text-align: center;
  49. }
  50. .xl-slider-wrap-btn {
  51. width: 12px;
  52. height: 12px;
  53. border-radius: 50%;
  54. background: #fff;
  55. display: inline-block;
  56. vertical-align: middle;
  57. cursor: pointer;
  58. transition: 0.3s;
  59. }
  60. .xl-slider-wrap-value {
  61. position: absolute;
  62. bottom: -50%;
  63. left: 42%;
  64. transform: translate(-50%, 0);
  65. }
  66. .xl-slider-wrap:after {
  67. content: '';
  68. height: 100%;
  69. display: inline-block;
  70. vertical-align: middle;
  71. }
  72. .xl-slider-wrap-btn:hover,
  73. .xl-slider-wrap-btn.xl-slider-hover {
  74. transform: scale(1.2);
  75. }
  76. .xl-slider-wrap-btn.xl-disabled:hover {
  77. transform: scale(1) !important;
  78. }
  79. .xl-slider-tips {
  80. position: absolute;
  81. top: -42px;
  82. z-index: 66666666;
  83. white-space: nowrap;
  84. display: none;
  85. -webkit-transform: translateX(-50%);
  86. transform: translateX(-50%);
  87. color: #fff;
  88. background: #000;
  89. border-radius: 3px;
  90. height: 25px;
  91. line-height: 25px;
  92. padding: 0 10px;
  93. }
  94. .xl-slider-tips:after {
  95. content: '';
  96. position: absolute;
  97. bottom: -12px;
  98. left: 50%;
  99. margin-left: -6px;
  100. width: 0;
  101. height: 0;
  102. border-width: 6px;
  103. border-style: solid;
  104. border-color: #000 transparent transparent transparent;
  105. }
  106. .xl-slider-input {
  107. width: 70px;
  108. height: 32px;
  109. border: 1px solid #eee;
  110. border-radius: 3px;
  111. font-size: 16px;
  112. line-height: 32px;
  113. position: absolute;
  114. right: 0;
  115. top: -14px;
  116. }
  117. .xl-slider-input-btn {
  118. position: absolute;
  119. top: 0;
  120. right: 0;
  121. width: 20px;
  122. height: 100%;
  123. border-left: 1px solid #eee;
  124. }
  125. .xl-slider-input-btn i {
  126. cursor: pointer;
  127. position: absolute;
  128. right: 0;
  129. bottom: 0;
  130. width: 20px;
  131. height: 50%;
  132. font-size: 12px;
  133. line-height: 16px;
  134. text-align: center;
  135. color: #999;
  136. }
  137. .xl-slider-input-btn i:first-child {
  138. top: 0;
  139. border-bottom: 1px solid #eee;
  140. }
  141. .xl-slider-input-txt {
  142. height: 100%;
  143. font-size: 14px;
  144. }
  145. .xl-slider-input-txt input {
  146. height: 100%;
  147. border: none;
  148. }
  149. .xl-slider-input-btn i:hover {
  150. color: #009688;
  151. }
  152. @media \0screen {
  153. .xl-slider-wrap-btn {
  154. margin-left: -20px;
  155. }
  156. .xl-slider > span {
  157. margin-left: 8px;
  158. }
  159. }
  160. /* 其它辅助 */
  161. .xl-auxiliar-moving {
  162. position: fixed;
  163. left: 0;
  164. right: 0;
  165. top: 0;
  166. bottom: 0;
  167. width: 100%;
  168. height: 100%;
  169. background: none;
  170. z-index: 9999999999;
  171. }
  172. </style>
  173. </head>
  174. <body>
  175. <div class="xl-container">
  176. <div id="slideTest1"></div>
  177. </div>
  178. <script>
  179. var Slider = function (options) {
  180. var that = this;
  181. that.config = Object.assign({}, that.config, options);
  182. that.render();
  183. };
  184. Slider.prototype.config = {
  185. min: 0, //最小值
  186. max: 100, //最大值,默认100
  187. value: 0, //初始值,默认为0
  188. range: false, //范围选择,默认关闭
  189. disabled: false, //滑块禁用,默认关闭
  190. theme: '#009688', //主题颜色
  191. step: 1, //间隔值
  192. };
  193. //滑块渲染
  194. Slider.prototype.render = function (params) {
  195. var that = this,
  196. options = that.config;
  197. var div = document.createElement('div');
  198. div.className = 'xl-slider';
  199. //间隔值不能小于 1
  200. if (options.step < 1) options.step = 1;
  201. //最大值不能小于最小值
  202. if (options.max < options.min) options.max = options.min + options.step;
  203. //判断是否开启双滑块
  204. if (options.range) {
  205. options.value =
  206. typeof options.value == 'object'
  207. ? options.value
  208. : [options.min, options.value];
  209. var minValue = Math.min(options.value[0], options.value[1]),
  210. maxValue = Math.max(options.value[0], options.value[1]);
  211. options.value[0] = minValue > options.min ? minValue : options.min;
  212. options.value[1] = maxValue > options.min ? maxValue : options.min;
  213. options.value[0] =
  214. options.value[0] > options.max ? options.max : options.value[0];
  215. options.value[1] =
  216. options.value[1] > options.max ? options.max : options.value[1];
  217. var scaleFir = Math.floor(
  218. ((options.value[0] - options.min) / (options.max - options.min)) *
  219. 100
  220. ),
  221. scaleSec = Math.floor(
  222. ((options.value[1] - options.min) / (options.max - options.min)) *
  223. 100
  224. ),
  225. scale = scaleSec - scaleFir + '%';
  226. scaleFir = scaleFir + '%';
  227. scaleSec = scaleSec + '%';
  228. } else {
  229. //如果初始值是一个数组,则获取数组的最小值
  230. if (typeof options.value == 'object') {
  231. options.value = Math.min.apply(null, options.value);
  232. }
  233. //初始值不能小于最小值且不能大于最大值
  234. if (options.value < options.min) options.value = options.min;
  235. if (options.value > options.max) options.value = options.max;
  236. var scale =
  237. Math.floor(
  238. ((options.value - options.min) / (options.max - options.min)) *
  239. 100
  240. ) + '%';
  241. }
  242. //如果禁用,颜色为统一的灰色
  243. var theme = options.disabled ? '#c2c2c2' : options.theme;
  244. //滑块
  245. var temp = `<div class="xl-slider-bar" style="background:${theme};width:${scale};left:${
  246. scaleFir || 0
  247. };"></div>
  248. <div class="xl-slider-wrap" style="left:${scaleFir || scale};">
  249. <div class="xl-slider-wrap-btn" style="border: 2px solid ${theme};"></div>
  250. <div class="xl-slider-wrap-value">${
  251. options.range ? options.value[0] : options.value
  252. }</div>
  253. </div>
  254. ${
  255. options.range
  256. ? `<div class="xl-slider-wrap" style="left: ${scaleSec};">
  257. <div class="xl-slider-wrap-btn" style="border: 2px solid ${theme};"></div>
  258. <div class="xl-slider-wrap-value">${options.value[1]}</div>
  259. </div>`
  260. : ''
  261. }
  262. </div>`;
  263. div.innerHTML = temp;
  264. that.container = document.querySelector(options.elem);
  265. that.container.appendChild(div);
  266. that.sliderInner = that.container.querySelector('.xl-slider');
  267. that.sliderBar = that.container.querySelector('.xl-slider-bar');
  268. that.sliderBtnWrap = that.container.querySelectorAll('.xl-slider-wrap');
  269. //把数据缓存到滑块上
  270. if (options.range) {
  271. that.sliderBtnWrap[0].dataset.value = options.value[0];
  272. that.sliderBtnWrap[1].dataset.value = options.value[1];
  273. } else {
  274. that.sliderBtnWrap[0].dataset.value = options.value;
  275. }
  276. //滑块滑动事件
  277. that.slide();
  278. };
  279. //滑块滑动
  280. Slider.prototype.slide = function (setValue, value, i) {
  281. var that = this,
  282. options = that.config,
  283. sliderAct = that.sliderInner,
  284. sliderWidth = function () {
  285. return sliderAct.offsetWidth;
  286. },
  287. sliderWrap = that.sliderBtnWrap,
  288. step = 100 / ((options.max - options.min) / Math.ceil(options.step)),
  289. change = function (offsetValue, index) {
  290. if (Math.ceil(offsetValue) * step > 100) {
  291. offsetValue = Math.ceil(offsetValue) * step;
  292. } else {
  293. offsetValue = Math.round(offsetValue) * step;
  294. }
  295. offsetValue = offsetValue > 100 ? 100 : offsetValue;
  296. sliderWrap[index].style.left = offsetValue + '%';
  297. var firLeft = valueTo(sliderWrap[0].offsetLeft),
  298. secLeft = options.range ? valueTo(sliderWrap[1].offsetLeft) : 0;
  299. firLeft = firLeft > 100 ? 100 : firLeft;
  300. secLeft = secLeft > 100 ? 100 : secLeft;
  301. var minLeft = Math.min(firLeft, secLeft),
  302. wrapWidth = Math.abs(firLeft - secLeft);
  303. that.sliderBar.style.width = wrapWidth + '%';
  304. that.sliderBar.style.left = minLeft + '%';
  305. var selfValue =
  306. options.min +
  307. Math.round(((options.max - options.min) * offsetValue) / 100);
  308. sliderWrap[index].dataset.value = selfValue;
  309. var inner = sliderWrap[index].querySelector(
  310. '.xl-slider-wrap-value'
  311. );
  312. inner.innerText = selfValue;
  313. //如果开启范围选择,则返回数组值
  314. if (options.range) {
  315. var arrValue = [
  316. +sliderWrap['0'].dataset.value,
  317. +sliderWrap['1'].dataset.value,
  318. ];
  319. if (arrValue[0] > arrValue[1]) arrValue.reverse(); //如果前面的圆点超过了后面的圆点值,则调换顺序
  320. }
  321. //回调
  322. options.change &&
  323. options.change(options.range ? arrValue : selfValue);
  324. },
  325. valueTo = function (value) {
  326. var oldLeft = ((value / sliderWidth()) * 100) / step,
  327. left = Math.round(oldLeft) * step;
  328. if (value == sliderWidth()) {
  329. left = Math.ceil(oldLeft) * step;
  330. }
  331. return left;
  332. },
  333. //拖拽元素
  334. createMoveElem = function (move, up) {
  335. var elemMove = document.getElementById('LAY-slider-moving');
  336. if (!elemMove) {
  337. var div = document.createElement('div');
  338. div.id = 'LAY-slider-moving';
  339. div.className = 'xl-auxiliar-moving';
  340. document.body.appendChild(div);
  341. elemMove = document.getElementById('LAY-slider-moving');
  342. }
  343. var upCall = function () {
  344. up && up();
  345. elemMove.parentNode.removeChild(elemMove);
  346. };
  347. elemMove.addEventListener('mousemove', move);
  348. elemMove.addEventListener('mouseup', upCall);
  349. elemMove.addEventListener('mouseleave', upCall);
  350. };
  351. //动态赋值
  352. if (setValue === 'set') return change(value, i);
  353. //滑块滑动
  354. that.sliderBtnWrap.forEach((dom, index) => {
  355. var elem = dom.querySelector('.xl-slider-wrap-btn');
  356. elem.addEventListener('mousedown', function (e) {
  357. e = e || window.event;
  358. var oldleft = elem.parentNode.offsetLeft,
  359. oldx = e.clientX;
  360. var move = function (e) {
  361. e = e || window.event;
  362. var left = oldleft + e.clientX - oldx;
  363. if (left < 0) left = 0;
  364. if (left > sliderWidth()) left = sliderWidth();
  365. var reaLeft = ((left / sliderWidth()) * 100) / step;
  366. change(reaLeft, index);
  367. e.preventDefault();
  368. };
  369. var up = function () {
  370. console.log('1--up');
  371. };
  372. createMoveElem(move, up);
  373. });
  374. });
  375. //点击滑块
  376. sliderAct.addEventListener('click', function (e) {
  377. var sliderRect = this.getBoundingClientRect();
  378. console.log(111, sliderRect);
  379. var left = e.clientX - sliderRect.left,
  380. index;
  381. if (left < 0) left = 0;
  382. if (left > sliderWidth()) left = sliderWidth();
  383. var reaLeft = ((left / sliderWidth()) * 100) / step;
  384. if (options.range) {
  385. index =
  386. Math.abs(left - sliderWrap[0].offsetLeft) >
  387. Math.abs(left - sliderWrap[1].offsetLeft)
  388. ? 1
  389. : 0;
  390. } else {
  391. index = 0;
  392. }
  393. change(reaLeft, index);
  394. e.preventDefault();
  395. });
  396. };
  397. // 监听变化
  398. Slider.prototype.onChange = function (callBack) {
  399. var that = this,
  400. options = that.config;
  401. options.change = callBack;
  402. };
  403. // 设置值
  404. Slider.prototype.setValue = function (value, index) {
  405. var that = this,
  406. options = that.config;
  407. options.value = value;
  408. return that.slide('set', value, index || 0);
  409. };
  410. var slider = new Slider({
  411. elem: '#slideTest1',
  412. min: 0,
  413. max: 300,
  414. value: [0, 100],
  415. range: true, //范围选择
  416. theme: 'skyblue',
  417. // change: function(value){ //回调实时显示当前值
  418. // console.log('change-1', value)
  419. // }
  420. });
  421. slider.onChange((value) => {
  422. console.log('change-2', value);
  423. });
  424. slider.setValue(200, 1)
  425. console.log(1231, slider);
  426. </script>
  427. </body>
  428. </html>