一、基础
js 隐式转换规则
- null == undefined, 但和其他值比较就不再相等了。
- NaN == NaN, 不相等。
- 对象 == 字符串,对象的toString()转为字符串
- 对象 == 数字,对象的toString()转为字符串,Number(对象的字符串)
- 剩下的都是转为数字比较。
for in 和 for of 的区别
a、for in 首先遍历对象的属性名,再是对象原型中的属性和方法, //如果不想让其输出原型中的属性和方法,可以使用hasOwnProperty方法进行过滤。也可以用Object.keys()方法获取所有的自身可枚举属性组成的数组<br /> b、for of 遍历对象时,要用Object.keys()先转成迭代器对象,例子:<br />
var student={name:'wujunchuan',age:22,locate:{country:'china'}}for(var key of Object.keys(student)){//使用Object.keys()方法获取对象key的数组console.log(key+": "+student[key]);}
案例解析
var getNumbers = () => {return Promise.resolve([1, 2, 3])}var multi = num => {return new Promise((resolve, reject) => {setTimeout(() => {if (num) {resolve(num * num)} else {reject(new Error('num not specified'))}}, 1000)})}async function test () {var nums = await getNumbers()nums.forEach(async x => {var res = await multi(x)console.log(res)})}test()
希望每间隔 1 秒,然后依次输出 1,4,9 。结果是一次性输出1,4,9
解决方案:
改造一下 forEach,确保每一个异步的回调执行完成后,才执行下一个
async function asyncForEach(array, callback) {for (let index = 0; index < array.length; index++) {await callback(array[index], index, array)}}async function test () {var nums = await getNumbers()asyncForEach(nums, async x => {var res = await multi(x)console.log(res)})}
使用 for-of 替代 for-each。
async function test () {var nums = await getNumbers()for(let x of nums) {var res = await multi(x)console.log(res)}}
二、编写规范
禁止副作用
const persons = [{name: 'liu', age: 18}, {name: 'wang', age: 22}];// badconst newPersions = persons.map(k => {k.hobby = 'swimming';return k;});// goodconst newPersions = persons.map(k => ({...k,hobby: 'swimming'}));
// badfunction addPersions(persions) {persions.push({name: '吴',age: 20});}addPersions(persons);// goodfunction addPersions(persions) {return [...persions, {name: '吴',age: 20}]}addPersions(persons);
const persons = [{name: 'liu', age: 18}, {name: 'wang', age: 22}];// badfunction sortPersions(persions) {// sort有副作用,会改变原数组const sortArr = persions.sort((a, b) => b.age - a.age);return sortArr;}sortPersions(persons);// goodfunction sortPersions(persions) {// sort有副作用,会改变原数组const sortArr = [...persions].sort((a, b) => b.age - a.age);return sortArr;}sortPersions(persons);
// very badconst prev = { coffee: 1 };const next = Object.assign(prev, { pizza: 42 });// badconst prev = { coffee: 1 };const next = Object.assign({}, prev, { pizza: 42 });// goodconst prev = { coffee: 1 };const next = { ...prev, pizza: 42 };
禁止不必要的条件
// badconst hasValue = value !== NONE ? true : false;const hasProducts = products.length > 0 ? true : false;// goodconst hasValue = value !== NONE;const hasProducts = products.length > 0;
// badconst hasValue = value ? true : false;const hasProducts = products.length ? true : false;// goodconst hasValue = Boolean(value);
// badconst hasProducts = products && Array.isArray(products);// goodconst hasProducts = Array.isArray(products);
// badfunction IsNetscapeOnSolaris() {var agent = window.navigator.userAgent;if (agent.indexOf('Mozilla') != -1 &&agent.indexOf('compatible') == -1) {if (agent.indexOf('SunOS') != -1) return true;else return false;} else {return false;}}// goodfunction IsNetscapeOnSolaris() {const { userAgent } = window.navigator;return (userAgent.includes('Mozilla') &&userAgent.includes('SunOS') &&!userAgent.includes('compatible'));}
// badif (errorMessage) {log(LOG_LEVEL.ERROR, errorMessage);} else {log(LOG_LEVEL.ERROR, DEFAULT_ERROR_MESSAGE);}// goodlog(LOG_LEVEL.ERROR, errorMessage || DEFAULT_ERROR_MESSAGE);
// badfunction getProductsDropdownItems(response) {const products = response.products;if (products.length > 0) {return products.map(product => ({label: product.name,value: product.id}));}return [];}// 优化后,减少了一层判断// 问题:当传入的products为undefined时,默认值才生效function getProductsDropdownItems(products = []) {return products.map(product => ({label: product.name,value: product.id}));}// 解决上面的问题function getProductsDropdownItems(products) {return (products || []).map(product => ({label: product.name,value: product.id}));}// 最终版本function getProductsDropdownItems({ products }) {(Array.isArray(products) ? products : [products]).map(product => ({label: product.name,value: product.id}));}
// badconst generateOptionalRows = () => {const rows = [];if (product1.colors.length + product2.colors.length > 0) {rows.push({row: 'Colors',product1: <ProductOptions options={product1.colors} />,product2: <ProductOptions options={product2.colors} />});}if (product1.sizes.length + product2.sizes.length > 0) {rows.push({row: 'Sizes',product1: <ProductOptions options={product1.sizes} />,product2: <ProductOptions options={product2.sizes} />});}return rows;};const rows = [{row: 'Name',product1: <Text>{product1.name}</Text>,product2: <Text>{product2.name}</Text>},// More rows......generateOptionalRows()];// goodconst rows = [{row: 'Name',product1: <Text>{product1.name}</Text>,product2: <Text>{product2.name}</Text>},{row: 'Colors',product1: <ProductOptions options={product1.colors} />,product2: <ProductOptions options={product2.colors} />,isVisible: (product1, product2) =>(product1.colors.length > 0 || product2.colors.length) > 0},{row: 'Sizes',product1: <ProductOptions options={product1.sizes} />,product2: <ProductOptions options={product2.sizes} />,isVisible: (product1, product2) =>(product1.sizes.length > 0 || product2.sizes.length) > 0}];const visibleRows = rows.filter(row => {if (typeof row.isVisible === 'function') {return row.isVisible(product1, product2);}return true;});
提前返回错误
// badfunction postOrderStatus(orderId) {var idsArrayObj = getOrderIds();if (idsArrayObj != undefined) {if (idsArrayObj.length == undefined) {var tmpBottle = idsArrayObj;idsArrayObj = new Array(tmpBottle);}var fullRecordsArray = new Array();if (fullRecordsArray.length != 0)return sendOrderStatus(fullRecordsArray);} else {return false;}} else {return false;}}
// 优化后function postOrderStatus(orderId) {let idsArrayObj = getOrderIds();if (idsArrayObj === undefined) {return false;}if (!Array.isArray(idsArrayObj)) {idsArrayObj = [idsArrayObj];}const fullRecordsArray = [];if (fullRecordsArray.length === 0) {return false;}return sendOrderStatus(fullRecordsArray);}
// 最终版function postOrderStatus(orderId) {const orderIds = getOrderIds(); // 总是返回数组const fullRecordsArray = [];if (fullRecordsArray.length === 0) {return false;}return sendOrderStatus(fullRecordsArray);}
多层if判断优化
// badif (month == 'jan') month = 1;if (month == 'feb') month = 2;if (month == 'mar') month = 3;if (month == 'apr') month = 4;if (month == 'may') month = 5;if (month == 'jun') month = 6;if (month == 'jul') month = 7;if (month == 'aug') month = 8;if (month == 'sep') month = 9;if (month == 'oct') month = 10;if (month == 'nov') month = 11;if (month == 'dec') month = 12;// goodconst MONTH_NAME_TO_NUMBER = {jan: 1,feb: 2,mar: 3,apr: 4,may: 5,jun: 6,jul: 7,aug: 8,sep: 9,oct: 10,nov: 11,dec: 12};const month = MONTH_NAME_TO_NUMBER[monthName];
const DECISION_YES = 0;const DECISION_NO = 1;const DECISION_MAYBE = 2;const getButtonLabel = decisionButton => {switch (decisionButton) {case DECISION_YES:return 'Yes';case DECISION_NO:return 'No';case DECISION_MAYBE:return 'Maybe';}};<Button>{getButtonLabel(decision.id)}</Button>;// goodconst DECISION_YES = 0;const DECISION_NO = 1;const DECISION_MAYBE = 2;const getButtonLabel = decisionButton =>({[DECISION_YES]: 'Yes',[DECISION_NO]: 'No',[DECISION_MAYBE]: 'Maybe'}[decisionButton]);<Button>{getButtonLabel(decision.id)}</Button>;// very goodconst DECISION_YES = 0;const DECISION_NO = 1;const DECISION_MAYBE = 2;const ButtonLabel = ({ decision }) =>({[DECISION_YES]: 'Yes',[DECISION_NO]: 'No',[DECISION_MAYBE]: 'Maybe'}[decision]);<Button><ButtonLabel decision={decision.id} /></Button>;
合理使用三元运算
// badlet drink;if (caffeineLevel < 50) {drink = DRINK_COFFEE;} else {drink = DRINK_WATER;}// goodconst drink = caffeineLevel < 50 ? DRINK_COFFEE : DRINK_WATER;
// badfunction Products({products, isError, isLoading}) {return isError? <p>Error loading products</p>: isLoading? <Loading />: products.length > 0? <ul>{products.map(product => <li key={product.id}>{product.name}</li>)}</ul>: <p>No products found</p>}// goodfunction Products({ products, isError, isLoading }) {if (isError) {return <p>Error loading products</p>;}if (isLoading) {return <Loading />;}if (products.length === 0) {return <p>No products found</p>;}return (<ul>{products.map(product => (<li key={product.id}>{product.name}</li>))}</ul>);}
二、相关实现
new的实现
function newFunc(...args) {const constructor = args.shift();const obj = Object.create(constructor.prototype);const result = constructor.apply(obj, args);return (typeof result === 'object' && result != null) ? result : obj;}
call的实现
Function.prototype.call3 = function (context, ...rest) {context = context ? Object(context) : window;const key = `_fn${Date.now()}`;context[key] = this;const result = context[key](...(rest || []));delete context[key];return result;}// 测试代码var value = 2;var obj = {value: 1,}function bar(name, age) {console.log(this.value);return {value: this.value,name,age,}}console.log(bar.call3(null))console.log(bar.call3(obj, 'kevin', 18));
apply的实现
Function.prototype.apply3 = function (context, rest = []) {context = context || window;const key = `fn${Date.now()}`;console.log(context, rest);context[key] = this;const result = context[key](...rest);delete context[key];return result;}var value = 2;var obj = {value: 1,}function bar(name, age) {console.log(this.value);return {value: this.value,name,age,}}console.log(bar.apply3(null))console.log(bar.apply3(obj, ['kevin', 18]));
bind的实现方式
Function.prototype.bind2 = function(content) {if(typeof this != "function") {throw Error("not a function")}// 若没问参数类型则从这开始写let fn = this;let args = [...arguments].slice(1);let resFn = function() {return fn.apply(this instanceof resFn ? this : content,args.concat(...arguments) )}function tmp() {}tmp.prototype = this.prototype;resFn.prototype = new tmp();return resFn;}
bind导致的内存泄漏
/** bind为null或undefined时,foo里面的this指向window,会在window上挂载a变量*/function foo(a, b) {this.a = 'test';console.log( "a:" + a + ",b:" + b );}var bar = foo.bind( null, 2 );bar( 3 ); // a:2,b:3/** 解决方法,使用Object.create, 0, false*/function foo(a, b) {console.log( "a:" + a + ",b:" + b );}// 我们的空对象var ø = Object.create( null );// 使用bind(..)进行柯里化var bar = foo.bind( ø, 2 );bar( 3 ); // a:2,b:3
箭头函数的bind、call、apply没有用
var fun = (() => { console.log(this);}).bind({name: 12})console.log(fun()); //window// 练习let result = (function() {return [(() => this.x).bind({x: 'inner'})(),(() => this.x)()]}).call({x: 'outer'})console.log(result) // [ 'outer', 'outer' ]
使用bind实现偏函数
解释:使一个函数拥有预设的初始参数;
function list() {return Array.prototype.slice.call(arguments);}function addArguments(arg1, arg2) {return arg1 + arg2}var list1 = list(1, 2, 3); // [1, 2, 3]var result1 = addArguments(1, 2); // 3// 创建一个函数,它拥有预设参数列表。var leadingThirtysevenList = list.bind(null, 37);// 创建一个函数,它拥有预设的第一个参数var addThirtySeven = addArguments.bind(null, 37);var list2 = leadingThirtysevenList();// [37]var list3 = leadingThirtysevenList(1, 2, 3);// [37, 1, 2, 3]var result2 = addThirtySeven(5);// 37 + 5 = 42var result3 = addThirtySeven(5, 10);// 37 + 5 = 42 ,第二个参数被忽略
bind和apply、call之间的异同
相同点:
- apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
- apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
- apply 、 call 、bind 三者都可以利用后续参数传参;
不同的:
- bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。
注意点:在Javascript中,多次 bind() 是无效的。
promise实现
// 先定义三个常量表示状态const PENDING = "pending";const FULFILLED = "fulfilled";const REJECTED = "rejected";// 新建 MyPromise 类class MyPromise {constructor(executor) {// executor 是一个执行器,进入会立即执行// 并传入resolve和reject方法try {executor(this.resolve, this.reject);} catch (error) {this.reject(error);}}// 储存状态的变量,初始值是 pendingstatus = PENDING;// resolve和reject为什么要用箭头函数?// 如果直接调用的话,普通函数this指向的是window或者undefined// 用箭头函数就可以让this指向当前实例对象// 成功之后的值value = null;// 失败之后的原因reason = null;// 存储成功回调函数onFulfilledCallbacks = [];// 存储失败回调函数onRejectedCallbacks = [];// 更改成功后的状态resolve = (value) => {// 只有状态是等待,才执行状态修改if (this.status === PENDING) {// 状态修改为成功this.status = FULFILLED;// 保存成功之后的值this.value = value;// resolve里面将所有成功的回调拿出来执行while (this.onFulfilledCallbacks.length) {// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空this.onFulfilledCallbacks.shift()(value);}}};// 更改失败后的状态reject = (reason) => {// 只有状态是等待,才执行状态修改if (this.status === PENDING) {// 状态成功为失败this.status = REJECTED;// 保存失败后的原因this.reason = reason;// resolve里面将所有失败的回调拿出来执行while (this.onRejectedCallbacks.length) {this.onRejectedCallbacks.shift()(reason);}}};then(onFulfilled, onRejected) {const realOnFulfilled =typeof onFulfilled === "function" ? onFulfilled : (value) => value;const realOnRejected =typeof onRejected === "function"? onRejected: (reason) => {throw reason;};// 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去const promise2 = new MyPromise((resolve, reject) => {const fulfilledMicrotask = () => {// 创建一个微任务等待 promise2 完成初始化queueMicrotask(() => {try {// 获取成功回调函数的执行结果const x = realOnFulfilled(this.value);// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject);} catch (error) {reject(error);}});};const rejectedMicrotask = () => {// 创建一个微任务等待 promise2 完成初始化queueMicrotask(() => {try {// 调用失败回调,并且把原因返回const x = realOnRejected(this.reason);// 传入 resolvePromise 集中处理resolvePromise(promise2, x, resolve, reject);} catch (error) {reject(error);}});};// 判断状态if (this.status === FULFILLED) {fulfilledMicrotask();} else if (this.status === REJECTED) {rejectedMicrotask();} else if (this.status === PENDING) {// 等待// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来// 等到执行成功失败函数的时候再传递this.onFulfilledCallbacks.push(fulfilledMicrotask);this.onRejectedCallbacks.push(rejectedMicrotask);}});return promise2;}catch(onRejected) {// 只需要进行错误处理this.then(undefined, onRejected);}finally(fn) {return this.then((value) => {return MyPromise.resolve(fn()).then(() => {return value;});},(error) => {return MyPromise.resolve(fn()).then(() => {throw error;});});}// resolve 静态方法static resolve(parameter) {// 如果传入 MyPromise 就直接返回if (parameter instanceof MyPromise) {return parameter;}// 转成常规方式return new MyPromise((resolve) => {resolve(parameter);});}// reject 静态方法static reject(reason) {return new MyPromise((resolve, reject) => {reject(reason);});}static all(promiseList) {return new MyPromise((resolve, reject) => {const result = [];const length = promiseList.length;let count = 0;if (length === 0) {return resolve(result);}promiseList.forEach((promise, index) => {MyPromise.resolve(promise).then((value) => {count++;result[index] = value;if (count === length) {resolve(result);}},(reason) => {reject(reason);});});});}static allSettled = (promiseList) => {return new MyPromise((resolve) => {const length = promiseList.length;const result = [];let count = 0;if (length === 0) {return resolve(result);} else {for (let i = 0; i < length; i++) {const currentPromise = MyPromise.resolve(promiseList[i]);currentPromise.then((value) => {count++;result[i] = {status: "fulfilled",value: value,};if (count === length) {return resolve(result);}},(reason) => {count++;result[i] = {status: "rejected",reason: reason,};if (count === length) {return resolve(result);}});}}});};static race(promiseList) {return new MyPromise((resolve, reject) => {const length = promiseList.length;if (length === 0) {return resolve();} else {for (let i = 0; i < length; i++) {MyPromise.resolve(promiseList[i]).then((value) => {return resolve(value);},(reason) => {return reject(reason);});}}});}}function resolvePromise(promise, x, resolve, reject) {// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise// 这是为了防止死循环if (promise === x) {return reject(new TypeError("The promise and the return value are the same"));}if (typeof x === "object" || typeof x === "function") {// 这个坑是跑测试的时候发现的,如果x是null,应该直接resolveif (x === null) {return resolve(x);}let then;try {// 把 x.then 赋值给 thenthen = x.then;} catch (error) {// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promisereturn reject(error);}// 如果 then 是函数if (typeof then === "function") {let called = false;// 将 x 作为函数的作用域 this 调用之// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise// 名字重名了,我直接用匿名函数了try {then.call(x,// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)(y) => {// 如果 resolvePromise 和 rejectPromise 均被调用,// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用// 实现这条需要前面加一个变量calledif (called) return;called = true;resolvePromise(promise, y, resolve, reject);},// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise(r) => {if (called) return;called = true;reject(r);});} catch (error) {// 如果调用 then 方法抛出了异常 e:// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之if (called) return;// 否则以 e 为据因拒绝 promisereject(error);}} else {// 如果 then 不是函数,以 x 为参数执行 promiseresolve(x);}} else {// 如果 x 不为对象或者函数,以 x 为参数执行 promiseresolve(x);}}MyPromise.deferred = function () {var result = {};result.promise = new MyPromise(function (resolve, reject) {result.resolve = resolve;result.reject = reject;});return result;};module.exports = MyPromise;
class MyPromise {constructor (fn) {this.fn = fn;this.resolve = v => v;const _resolve = (v) => {this.status = 'resolve'this.resolve(v)this.res = v}this.fn(_resolve)}then(cb) {const wrapperCb = val => {if (val.constructor === MyPromise) {return val.then(cb)} else {return cb(val)}}const resolve = this.resolvethis.resolve = (...args) => wrapperCb(resolve(...args))// 检测状态 未完成就挂起 完成就执行if(this.status === 'resolve') {this.resolve(this.res)}return this}}var data = new MyPromise((res) => {setTimeout(() => {console.log('start');res('start')}, 2000)// res('start')})data.then((res) => {// return res + 44console.log('res1');return new MyPromise((res2) => setTimeout(() => res2(res + '11'), 1000));// return new MyPromise((res2) => res2(res + '11'));}).then((res) => {// return res + 44console.log('res2');// return new MyPromise((res2) => setTimeout(() => res2(res + '11'), 1000));return new MyPromise((res2) => res2(res + '22'));}).then(console.log);
深拷贝
- 方式一
```javascript
function getEmpty(o) {
const types = {
} const type = Object.prototype.toString.call(o); if(types[type]) {'[object Object]': {},'[object Array]': [],
} return o; }return types[type]
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);
2. 方式二```javascriptfunction deepClone(obj) {if(obj == null || typeof obj !== 'object') return obj// 时间对象有特殊属性const newObj = new obj.constructor(obj.constructor === Date ? obj : null)// 不拷贝原型链的for (const key in Object.getOwnPropertyDescriptors(obj)) {newObj[key] = deepClone(obj[key])}return newObj}
三、函数式编程
函数柯里化
once实现
const once = () => {let done = false;return function(){return done?undefined: ((done = true), fn.apply(this,arguments))}}
memoized实现
const memoized = () => {const lookupTable = {};return (arg) => lookupTable[arg] || (lookupTable[arg] = fn(arg))}
unary实现
// 让函数只接受一个参数const unary = (fn) => fn.length === 1 ? fn: (arg) => fn(arg)
四、相关应用
switch里面case和default一起使用
switch(step){case 1 : default: {html='<h1></h1>'; break;}case 2:{html = '<h2></h2>' ; break;}case 3:{html = '<h3></h3>'; break;}}
小数求最大值、最小值
const readings = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4];const maxReading = readings.reduce((x, y) => Math.max(x, y), Number.MIN_VALUE);const minReading = readings.reduce((x, y) => Math.min(x, y), Number.MAX_VALUE);console.log({minReading, maxReading});// ⦘ {minReading: 0.2, maxReading: 5.5}
点击区域以外
const handler = event => {const { current: el } = ref;el && !el.contains(event.target) && onClickAway(event);};document.body.addEventListener('click', e => {if (e.target && e.target.matches('div.code')) {return;}// do sthing});
判断函数参数的个数
var a = function(a){}console.log(a.lenth); // 1var b = function(a,b){}console.log(b.lenth); // 2// 让函数只接受一个参数const unary = () => fn.length === 1 ? fn : (arg) => fn(arg)
发布订阅模式
class Event{constructor(){this.callbacks = {}}$off(name){this.callbacks[name] = null}$emit(name, args){let cbs = this.callbacks[name]if (cbs) {cbs.forEach(c=>{c.call(this, args)})}}$on(name, fn){(this.callbacks[name] || (this.callbacks[name] = [])).push(fn)}}let event = new Event()event.$on('event1', function(arg){console.log('事件1',arg)})event.$emit('event1',{name:'测试'})
axios
const axios = require('axios');const instance = axios.create({baseURL: 'http://localhost:3000/api'});const METHODS = ['get', 'post', 'patch'];const api = new Proxy({}, {get: (_, name) => new Proxy({}, {get:(_, method) => METHODS.includes(method) && new Proxy(() => {}, {apply:(_, self, [config]) => instance.request({url: name,method,...config,})})})})api.user.get({params: {id: 12}}).then((user) => console.log(user)).catch(console.error)
装饰器
@testDecclass Demo {// ...}function testDec(targe){target.isDec = true;}alert(Demo.isDec) // true
js异步校验
var fns = [v => v.length > 1,async (v) => new Promise((res, rej) => setTimeout(() => {if(v.length <= 2) {rej('小于2')} else {res('ok')}}, 1000)),v => v.length > 5,];async function validate(val, fns) {let res;for (let i = 0; i < fns.length; i++) {const fn = fns[i];if(i === 0) {res = fn(val) ? Promise.resolve(fn(val)) : Promise.reject(fn(val))} else {res = res.then(() => fn(val))}}return res;}validate('134', fns).then(res => {console.log('then', res);}).catch(err => {console.log('catch', err);})
五、相关工具函数
/** 数据格式化 **/function formatSize(size: number): string {let subFix: number = 0;let temp: number = size;let fixArry: string[] = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];while (temp >= 1024) {if (subFix > fixArry.length - 1) {break;}temp = temp / 1024;subFix++;}let fixExt: string = fixArry[subFix];let stringSize: string = temp.toFixed(2) + fixExt;return stringSize;}function bytesToSize (bytes, point = 1) {if (bytes === 0) {return '0 B'}const k = 1024 // or 1024const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']const i = Math.floor(Math.log(bytes) / Math.log(k))const val = bytes / Math.pow(k, i)let size = val.toPrecision(3)size = val.toFixed(point)return size + sizes[i]}function secondToDate (seconds) {const h = Math.floor(seconds / 3600).toString()const m = Math.floor((seconds / 60 % 60)).toString()const s = Math.floor((seconds % 60)).toString()return `${h.padStart(2, '0')}:${m.padStart(2, '0')}:${s.padStart(2, '0')}`}function fullscreen () {const ele = document.documentElementif (ele.requestFullscreen) {ele.requestFullscreen()} else if (ele.mozRequestFullScreen) {ele.mozRequestFullScreen()} else if (ele.webkitRequestFullScreen) {ele.webkitRequestFullScreen()setTimeout(() => {console.error('fullscreen success')document.dispatchEvent(new Event('fullscreenchange'))}, 0)}}/** 时间格式化 **/function parseTime (time, cFormat) {if (arguments.length === 0) {return null}const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'let dateif (typeof time === 'object') {date = time} else {if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {time = parseInt(time)}if ((typeof time === 'number') && (time.toString().length === 10)) {time = time * 1000}date = new Date(time)}const formatObj = {y: date.getFullYear(),m: date.getMonth() + 1,d: date.getDate(),h: date.getHours(),i: date.getMinutes(),s: date.getSeconds(),a: date.getDay()}const timeStr = format.replace(/{([ymdhisa])+}/g, (result, key) => {const value = formatObj[key]// Note: getDay() returns 0 on Sundayif (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }return value.toString().padStart(2, '0')})return timeStr}/** url参数解析 **/function getSearchParams () {const querySearch = location.search.slice(1)const searchMap = {}querySearch.split('&').map(item => item.split('=')).map(item => searchMap[item[0]] = item[1])return searchMap.from}
import * as Comlink from 'comlink'import CryptoJS from 'crypto-js'class Gcid {constructor () {this.gcid = ''this.gcidSHA1 = ''}create () {this.gcid = ''this.gcidSHA1 = CryptoJS.algo.SHA1.create()}calculate (ab, blockSize) {const size = ab.byteLengthconst blockNum = size / blockSizefor (let i = 0; i < blockNum; i++) {const wa = CryptoJS.lib.WordArray.create(ab.slice(blockSize * i, blockSize * (i + 1)))const bcidSHA1 = CryptoJS.SHA1(wa)this.gcidSHA1.update(bcidSHA1)}if (blockSize * blockNum < size) {const wa = CryptoJS.lib.WordArray.create(ab.slice(blockSize * blockNum, size))const bcidSHA1 = CryptoJS.SHA1(wa)this.gcidSHA1.update(bcidSHA1)}}finalize () {this.gcid = this.gcidSHA1.finalize().toString().toUpperCase()console.log('worker计算出来的gcid', this.gcid)}}Comlink.expose(Gcid)
import store from 'store'import storeExpire from 'store/plugins/expire'import OSS from 'ali-oss'import * as Comlink from 'comlink'import { createFile, saveOSSFile } from '@/api/drive'import { getCookie } from '@/utils/util'import constants from '@/utils/constants'// ali-oss 文档// https://help.aliyun.com/document_detail/64047.html?spm=a2c4g.11186623.6.1328.155479f8sfeFfZ#title-vbx-3di-xnh// let gcidWorker = nullstore.addPlugin(storeExpire)function calculateBlockSize (size) {if (size >= 0 && size <= (128 << 20)) {return 256 << 10}if (size > (128 << 20) && size <= (256 << 20)) {return 512 << 10}if (size > (256 << 20) && size <= (512 << 20)) {return 1024 << 10}return 2048 << 10}export async function readFileGcid (file, cb) {console.log('create gcidWorker start', new Date().getTime())let gcidWorker = nullif (!gcidWorker) {const MyWorker = Comlink.wrap(new Worker(`${location.origin}/gcidWorker.js`))gcidWorker = await new MyWorker()}await gcidWorker.create()// console.log('create gcidWorker end', new Date().getTime())return new Promise((resolve, reject) => {// console.log('create fileReader start', new Date().getTime())const reader = new window.FileReader() // FileReader实例// console.log('create fileReader end', new Date().getTime())// console.log('create blocksize start', new Date().getTime())const blockSize = calculateBlockSize(file.size)// console.log('create blocksize end', new Date().getTime())const CHUNK_MIN_SIZE = 100 * 1024 * 1024const CHUNK_SIZE = Math.floor(CHUNK_MIN_SIZE / blockSize) * blockSize // 以blockSize为单位做最大分片(小于100M)const CHUNK_LEN = Math.ceil(file.size / CHUNK_SIZE)let reverse_index = 0// console.log(`每${CHUNK_SIZE}大小分片读取文件中~~`, CHUNK_LEN)reader.onload = async () => {// console.log('onload')let calStart = new Date().getTime()// console.log('create calculate start', calStart)// console.log(reader.result)// console.log(blockSize)await gcidWorker.calculate(reader.result, blockSize)let calend = new Date().getTime()// console.log('create calculate end', calend)// console.log("cal cost :", calend - calStart)reverse_index += 1let ret = cb()if (ret === constants.UPLOAD_PAUSE) {return resolve(constants.UPLOAD_PAUSE)}// return resolve(constants.UPLOAD_PAUSE)// progress((10 * reverse_index / CHUNK_LEN).toFixed(2))// 删除情况// if (!file) {// return resolve(null)// }// 通过 file status 管理文件任务状态// if (!checkUploadTask(file.name)) {// progress(-2)// return resolve(null)// }if (reverse_index >= CHUNK_LEN) {// console.log('create calculate finalize start', new Date().getTime())await gcidWorker.finalize()// console.log('create calculate finalize end', new Date().getTime())const gcid = await gcidWorker.gcidgcidWorker = nullresolve(gcid)} else {seek()}}seek();function seek() {let start = CHUNK_SIZE * reverse_index;let end = (reverse_index >= CHUNK_LEN - 1) ? file.size : CHUNK_SIZE * (reverse_index + 1);let slice = file.slice(start, end);reader.readAsArrayBuffer(slice);}})}async function promiseCreateFile (params) {return new Promise(async (resolve) => {try {const result = await createFile(params)resolve([null, result])} catch(err) {resolve([err, null])}})}// 云添加使用的Url上传async function uploadUrl ({name = '',size = 0,url,files = [],parentId = '',kind = 'drive#file',unionId = '',require_links = false}) {let hash = ''const createParmas = {upload_type: 'UPLOAD_TYPE_URL',kind,parent_id: parentId,name,hash,size,url: {url,files},unionId,// 创建文件请求中, 增加"params": { "require_links": "true" } ,表示需要直接返回播放链接(边添加边播模式)params: {require_links: String(require_links)}}const [err, fileData] = await promiseCreateFile(createParmas)if (err) return errreturn fileData}// 使用URL进行云添加async function uploadUrlV2 ({name = '',size = 0,url,files = [],parentId = '',kind = 'drive#file',unionId = '',require_links = false}) {let hash = ''const createParams = {upload_type: 'UPLOAD_TYPE_URL',kind,parent_id: parentId,name,hash,size,url: {url,files},unionId,// 创建文件请求中, 增加"params": { "require_links": "true" } ,表示需要直接返回播放链接(边添加边播模式)params: {require_links: String(require_links)}}return promiseCreateFile(createParams);}// 统一上传接口async function uploadFile ({file,maxSize = 5,type = 'UPLOAD_TYPE_FORM',parentId = '',kind = 'drive#file',gcid,fileId,xlUploadCache,progress = (p) => null,createCb}) {console.log('uploadfile')let uploadType = type// 临时file.id,用于处理还在计算gcid阶段,删除操作. id 使用时间戳// createCb && createCb(Object.assign({}, file, {// id: new Date().getTime()// }))// if (!xlUploadCache) {// // 没有缓存时才重新计算gcid// console.log('得到的gcid', gcid)// }// console.log('文件名: --------')// console.log('xlUploadCache: ' ,xlUploadCache)// console.log(file.name)// return gcidconst createParmas = {kind,parent_id: parentId,name: file.name,hash: gcid,size: file.size}if (file.size > maxSize * 1024 * 1024) {// 大文件使用断点分片上传uploadType = 'UPLOAD_TYPE_RESUMABLE'}// TODO 优化 使用 接口返回的进行上传createParmas.upload_type = uploadTypeconsole.log('uploadfile1')if (uploadType === 'UPLOAD_TYPE_FORM') {// 表单上传,返回oss地址const [err, createRes] = await promiseCreateFile(createParmas)if (err) {console.log(err)progress(constants.UPLOAD_FAIL)return}createCb && createCb(createRes.file, createRes.upload_type)if (createRes.file && createRes.file.phase === 'PHASE_TYPE_COMPLETE') {return createRes.file}if (!createRes.form) {// 如果没有form,可能为秒传return createRes.file}console.log('uploadfile2')try {await saveOSSFile(createRes.form.url, {...createRes.form.multi_parts,file}, {headers: { 'Content-Type': 'multipart/form-data' },onUploadProgress: function(progressEvent) {let percentCompleted = ((progressEvent.loaded * 100) / progressEvent.total).toFixed(0)let fakeCheckPoint = {fileSize: progressEvent.total}progress(percentCompleted, fakeCheckPoint, null)}})return createRes.file} catch(err) {return Promise.resolve(null)}} else {let clientObj = xlUploadCacheconst [err, createRes] = await promiseCreateFile(createParmas)if (err) {console.log(err)progress(constants.UPLOAD_FAIL)return}if (!createRes.file) return createResif (createRes.file && createRes.file.phase === 'PHASE_TYPE_COMPLETE') {return createRes.file}if (!createRes.resumable) {// 如果没有resumable,可能为秒传return createRes.file}if (!xlUploadCache) {// 没有缓存记录,重新创建oss地址createCb && createCb(createRes.file, createRes.upload_type) // 回调更新文件id,用于删除if (createRes.upload_type === 'UPLOAD_TYPE_FORM') {// 服务端不支持分片上传if (file.size > 100 * 1024 * 1024) return Promise.resolve(null)const timeout = parseInt(file.size / (200 * 1024))try {await saveOSSFile(createRes.form.url, {...createRes.form.multi_parts,file}, {headers: { 'Content-Type': 'multipart/form-data' },timeout: timeout * 1000,onUploadProgress: function(progressEvent) {let percentCompleted = ((progressEvent.loaded * 100) / progressEvent.total).toFixed(0)let fakeCheckPoint = {fileSize: progressEvent.total}progress(percentCompleted, fakeCheckPoint, null)}})return createRes.file} catch(err) {return Promise.resolve(null)}}clientObj = createRes.resumable.paramsclientObj.file = createRes.file} else {createCb && createCb(clientObj.file, 'UPLOAD_TYPE_RESUMABLE') // 回调更新文件id,用于删除}let ossParams = {endpoint: clientObj.endpoint,accessKeyId: clientObj.access_key_id,accessKeySecret: clientObj.access_key_secret,bucket: clientObj.bucket,stsToken: clientObj.security_token,secure: true}const client = new OSS(ossParams)// browser 分片上传不需要initMultipartUpload和completeMultipartUpload// 有效期const validTime = new Date(clientObj.expiration)if (xlUploadCache) {// 缓存时 checkpoint.file 会丢失clientObj.checkpoint.file = file}// 暂停分片上传 client.cancel()const uploadRes = await client.multipartUpload(clientObj.key, file, {checkpoint: clientObj.checkpoint || '',// parallel: 3,partSize: 3 * 1024 * 1024,progress: (p, checkpoint) => {progress((p * 100).toFixed(2), checkpoint, client)// 缓存断点的记录数据const saveObj = {...clientObj,fileId,checkpoint: {...checkpoint,file: null}}store.set(`xlUploadCache-${saveObj.fileId}`, saveObj, new Date(validTime).getTime())// if (!checkUploadTask(file.name)) {// client.cancel()// }}}).then(res => {delUploadStore(fileId)return res}).catch(err => {progress(constants.UPLOAD_FAIL)// if (!checkUploadTask(file.name)) {// progress(-2)// } else {// if (err.status !== 0) {// progress(constants.UPLOAD_FAIL)// } else {// progress(constants.UPLOAD_PAUSE)// }// }})if (uploadRes && uploadRes.name) {return clientObj.file}}}export async function checkUpload (fileId) {// 检测断点上传的数据let xlUploadCache = store.get(`xlUploadCache-${fileId}`)if (xlUploadCache) {if (xlUploadCache.fileId === fileId) {return xlUploadCache}}return null}export async function delUploadStore (fileId) {store.remove(`xlUploadCache-${fileId}`)}function checkUploadTask (name) {// 检测当前上传任务是否存在// 或者可使用window全局变量来控制let val = getCookie('xlUploadTask')val = val ? decodeURIComponent(val) : nullif (val === name) return truereturn false}/** 新上传接口,支持多任务同时上传,支持同一时刻上传任务数量限制* @params*/async function uploadFileV2 ({file,maxSize = 5,type = 'UPLOAD_TYPE_FORM',parentId = '',kind = 'drive#file',gcid,fileId,xlUploadCache,progress = (p) => null,createCb}) {let uploadType = typeconst createParmas = {kind,parent_id: parentId === '*' ? '' : parentId,name: file.name,hash: gcid,size: file.size}if (file.size > maxSize * 1024 * 1024) {// 大文件使用断点分片上传uploadType = 'UPLOAD_TYPE_RESUMABLE'}// TODO 优化 使用 接口返回的进行上传createParmas.upload_type = uploadTypeconst [err, createRes] = await promiseCreateFile(createParmas)if (err) {console.log(err)// progress(constants.UPLOAD_FAIL)createCb && createCb(null, null, err)return}createCb && createCb(createRes.file, createRes.upload_type)if (createRes.file && createRes.file.phase === 'PHASE_TYPE_COMPLETE') {return createRes.file}if (createRes.upload_type === constants.UPLOAD_TYPE_FORM) {if (!createRes.form) {// 如果没有form,可能为秒传return createRes.file}// OSS上传try {await saveOSSFile(createRes.form.url, {...createRes.form.multi_parts,file}, {headers: { 'Content-Type': 'multipart/form-data' },onUploadProgress: function(progressEvent) {let percentCompleted = ((progressEvent.loaded * 100) / progressEvent.total).toFixed(0)let fakeCheckPoint = {fileSize: progressEvent.total}progress(percentCompleted, fakeCheckPoint, null)}})return createRes.file} catch(err) {return Promise.resolve(null)}} else if (createRes.upload_type === constants.UPLOAD_TYPE_UNKNOWN) {if (createRes.file && createRes.file.phase === 'PHASE_TYPE_COMPLETE') {return createRes.file}} else {let clientObj = xlUploadCacheif (!xlUploadCache) {clientObj = createRes.resumable.paramsclientObj.file = createRes.file}// 分片上传let ossParams = {endpoint: clientObj.endpoint,accessKeyId: clientObj.access_key_id,accessKeySecret: clientObj.access_key_secret,bucket: clientObj.bucket,stsToken: clientObj.security_token,secure: true}const client = new OSS(ossParams)// browser 分片上传不需要initMultipartUpload和completeMultipartUpload// 有效期const validTime = new Date(clientObj.expiration)if (xlUploadCache) {// 缓存时 checkpoint.file 会丢失clientObj.checkpoint.file = file}// 暂停分片上传 client.cancel()const uploadRes = await client.multipartUpload(clientObj.key, file, {checkpoint: clientObj.checkpoint || '',// parallel: 3,partSize: 3 * 1024 * 1024,progress: (p, checkpoint) => {progress((p * 100).toFixed(2), checkpoint, client)// 缓存断点的记录数据const saveObj = {...clientObj,fileId,checkpoint: {...checkpoint,file: null}}store.set(`xlUploadCache-${saveObj.fileId}`, saveObj, new Date(validTime).getTime())}}).then(res => {delUploadStore(fileId)return res}).catch(err => {return Promise.resolve(null)})if (uploadRes && uploadRes.name) {return clientObj.file}}}export default {uploadUrl,uploadUrlV2,uploadFile,uploadFileV2,checkUploadTask,checkUpload,delUploadStore,readFileGcid}
滑块实现
<!DOCTYPE html><html><head><meta charset="utf-8" /><metaname="viewport"content="width=device-width, initial-scale=1, maximum-scale=1"/><title>滑块</title><style>body {padding: 100px 0;}.xl-container {padding: 20px;}/** 滑块 **/.xl-slider {height: 4px;background: #eee;border-radius: 3px;position: relative;cursor: pointer;}.xl-slider-bar {border-radius: 3px;position: absolute;height: 100%;}.xl-slider-step {position: absolute;top: 0;width: 4px;height: 4px;border-radius: 50%;background: #fff;-webkit-transform: translateX(-50%);transform: translateX(-50%);}.xl-slider-wrap {width: 36px;height: 36px;position: absolute;top: -16px;-webkit-transform: translateX(-50%);transform: translateX(-50%);z-index: 10;text-align: center;}.xl-slider-wrap-btn {width: 12px;height: 12px;border-radius: 50%;background: #fff;display: inline-block;vertical-align: middle;cursor: pointer;transition: 0.3s;}.xl-slider-wrap-value {position: absolute;bottom: -50%;left: 42%;transform: translate(-50%, 0);}.xl-slider-wrap:after {content: '';height: 100%;display: inline-block;vertical-align: middle;}.xl-slider-wrap-btn:hover,.xl-slider-wrap-btn.xl-slider-hover {transform: scale(1.2);}.xl-slider-wrap-btn.xl-disabled:hover {transform: scale(1) !important;}.xl-slider-tips {position: absolute;top: -42px;z-index: 66666666;white-space: nowrap;display: none;-webkit-transform: translateX(-50%);transform: translateX(-50%);color: #fff;background: #000;border-radius: 3px;height: 25px;line-height: 25px;padding: 0 10px;}.xl-slider-tips:after {content: '';position: absolute;bottom: -12px;left: 50%;margin-left: -6px;width: 0;height: 0;border-width: 6px;border-style: solid;border-color: #000 transparent transparent transparent;}.xl-slider-input {width: 70px;height: 32px;border: 1px solid #eee;border-radius: 3px;font-size: 16px;line-height: 32px;position: absolute;right: 0;top: -14px;}.xl-slider-input-btn {position: absolute;top: 0;right: 0;width: 20px;height: 100%;border-left: 1px solid #eee;}.xl-slider-input-btn i {cursor: pointer;position: absolute;right: 0;bottom: 0;width: 20px;height: 50%;font-size: 12px;line-height: 16px;text-align: center;color: #999;}.xl-slider-input-btn i:first-child {top: 0;border-bottom: 1px solid #eee;}.xl-slider-input-txt {height: 100%;font-size: 14px;}.xl-slider-input-txt input {height: 100%;border: none;}.xl-slider-input-btn i:hover {color: #009688;}@media \0screen {.xl-slider-wrap-btn {margin-left: -20px;}.xl-slider > span {margin-left: 8px;}}/* 其它辅助 */.xl-auxiliar-moving {position: fixed;left: 0;right: 0;top: 0;bottom: 0;width: 100%;height: 100%;background: none;z-index: 9999999999;}</style></head><body><div class="xl-container"><div id="slideTest1"></div></div><script>var Slider = function (options) {var that = this;that.config = Object.assign({}, that.config, options);that.render();};Slider.prototype.config = {min: 0, //最小值max: 100, //最大值,默认100value: 0, //初始值,默认为0range: false, //范围选择,默认关闭disabled: false, //滑块禁用,默认关闭theme: '#009688', //主题颜色step: 1, //间隔值};//滑块渲染Slider.prototype.render = function (params) {var that = this,options = that.config;var div = document.createElement('div');div.className = 'xl-slider';//间隔值不能小于 1if (options.step < 1) options.step = 1;//最大值不能小于最小值if (options.max < options.min) options.max = options.min + options.step;//判断是否开启双滑块if (options.range) {options.value =typeof options.value == 'object'? options.value: [options.min, options.value];var minValue = Math.min(options.value[0], options.value[1]),maxValue = Math.max(options.value[0], options.value[1]);options.value[0] = minValue > options.min ? minValue : options.min;options.value[1] = maxValue > options.min ? maxValue : options.min;options.value[0] =options.value[0] > options.max ? options.max : options.value[0];options.value[1] =options.value[1] > options.max ? options.max : options.value[1];var scaleFir = Math.floor(((options.value[0] - options.min) / (options.max - options.min)) *100),scaleSec = Math.floor(((options.value[1] - options.min) / (options.max - options.min)) *100),scale = scaleSec - scaleFir + '%';scaleFir = scaleFir + '%';scaleSec = scaleSec + '%';} else {//如果初始值是一个数组,则获取数组的最小值if (typeof options.value == 'object') {options.value = Math.min.apply(null, options.value);}//初始值不能小于最小值且不能大于最大值if (options.value < options.min) options.value = options.min;if (options.value > options.max) options.value = options.max;var scale =Math.floor(((options.value - options.min) / (options.max - options.min)) *100) + '%';}//如果禁用,颜色为统一的灰色var theme = options.disabled ? '#c2c2c2' : options.theme;//滑块var temp = `<div class="xl-slider-bar" style="background:${theme};width:${scale};left:${scaleFir || 0};"></div><div class="xl-slider-wrap" style="left:${scaleFir || scale};"><div class="xl-slider-wrap-btn" style="border: 2px solid ${theme};"></div><div class="xl-slider-wrap-value">${options.range ? options.value[0] : options.value}</div></div>${options.range? `<div class="xl-slider-wrap" style="left: ${scaleSec};"><div class="xl-slider-wrap-btn" style="border: 2px solid ${theme};"></div><div class="xl-slider-wrap-value">${options.value[1]}</div></div>`: ''}</div>`;div.innerHTML = temp;that.container = document.querySelector(options.elem);that.container.appendChild(div);that.sliderInner = that.container.querySelector('.xl-slider');that.sliderBar = that.container.querySelector('.xl-slider-bar');that.sliderBtnWrap = that.container.querySelectorAll('.xl-slider-wrap');//把数据缓存到滑块上if (options.range) {that.sliderBtnWrap[0].dataset.value = options.value[0];that.sliderBtnWrap[1].dataset.value = options.value[1];} else {that.sliderBtnWrap[0].dataset.value = options.value;}//滑块滑动事件that.slide();};//滑块滑动Slider.prototype.slide = function (setValue, value, i) {var that = this,options = that.config,sliderAct = that.sliderInner,sliderWidth = function () {return sliderAct.offsetWidth;},sliderWrap = that.sliderBtnWrap,step = 100 / ((options.max - options.min) / Math.ceil(options.step)),change = function (offsetValue, index) {if (Math.ceil(offsetValue) * step > 100) {offsetValue = Math.ceil(offsetValue) * step;} else {offsetValue = Math.round(offsetValue) * step;}offsetValue = offsetValue > 100 ? 100 : offsetValue;sliderWrap[index].style.left = offsetValue + '%';var firLeft = valueTo(sliderWrap[0].offsetLeft),secLeft = options.range ? valueTo(sliderWrap[1].offsetLeft) : 0;firLeft = firLeft > 100 ? 100 : firLeft;secLeft = secLeft > 100 ? 100 : secLeft;var minLeft = Math.min(firLeft, secLeft),wrapWidth = Math.abs(firLeft - secLeft);that.sliderBar.style.width = wrapWidth + '%';that.sliderBar.style.left = minLeft + '%';var selfValue =options.min +Math.round(((options.max - options.min) * offsetValue) / 100);sliderWrap[index].dataset.value = selfValue;var inner = sliderWrap[index].querySelector('.xl-slider-wrap-value');inner.innerText = selfValue;//如果开启范围选择,则返回数组值if (options.range) {var arrValue = [+sliderWrap['0'].dataset.value,+sliderWrap['1'].dataset.value,];if (arrValue[0] > arrValue[1]) arrValue.reverse(); //如果前面的圆点超过了后面的圆点值,则调换顺序}//回调options.change &&options.change(options.range ? arrValue : selfValue);},valueTo = function (value) {var oldLeft = ((value / sliderWidth()) * 100) / step,left = Math.round(oldLeft) * step;if (value == sliderWidth()) {left = Math.ceil(oldLeft) * step;}return left;},//拖拽元素createMoveElem = function (move, up) {var elemMove = document.getElementById('LAY-slider-moving');if (!elemMove) {var div = document.createElement('div');div.id = 'LAY-slider-moving';div.className = 'xl-auxiliar-moving';document.body.appendChild(div);elemMove = document.getElementById('LAY-slider-moving');}var upCall = function () {up && up();elemMove.parentNode.removeChild(elemMove);};elemMove.addEventListener('mousemove', move);elemMove.addEventListener('mouseup', upCall);elemMove.addEventListener('mouseleave', upCall);};//动态赋值if (setValue === 'set') return change(value, i);//滑块滑动that.sliderBtnWrap.forEach((dom, index) => {var elem = dom.querySelector('.xl-slider-wrap-btn');elem.addEventListener('mousedown', function (e) {e = e || window.event;var oldleft = elem.parentNode.offsetLeft,oldx = e.clientX;var move = function (e) {e = e || window.event;var left = oldleft + e.clientX - oldx;if (left < 0) left = 0;if (left > sliderWidth()) left = sliderWidth();var reaLeft = ((left / sliderWidth()) * 100) / step;change(reaLeft, index);e.preventDefault();};var up = function () {console.log('1--up');};createMoveElem(move, up);});});//点击滑块sliderAct.addEventListener('click', function (e) {var sliderRect = this.getBoundingClientRect();console.log(111, sliderRect);var left = e.clientX - sliderRect.left,index;if (left < 0) left = 0;if (left > sliderWidth()) left = sliderWidth();var reaLeft = ((left / sliderWidth()) * 100) / step;if (options.range) {index =Math.abs(left - sliderWrap[0].offsetLeft) >Math.abs(left - sliderWrap[1].offsetLeft)? 1: 0;} else {index = 0;}change(reaLeft, index);e.preventDefault();});};// 监听变化Slider.prototype.onChange = function (callBack) {var that = this,options = that.config;options.change = callBack;};// 设置值Slider.prototype.setValue = function (value, index) {var that = this,options = that.config;options.value = value;return that.slide('set', value, index || 0);};var slider = new Slider({elem: '#slideTest1',min: 0,max: 300,value: [0, 100],range: true, //范围选择theme: 'skyblue',// change: function(value){ //回调实时显示当前值// console.log('change-1', value)// }});slider.onChange((value) => {console.log('change-2', value);});slider.setValue(200, 1)console.log(1231, slider);</script></body></html>
