一、基础
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}];
// bad
const newPersions = persons.map(k => {
k.hobby = 'swimming';
return k;
});
// good
const newPersions = persons.map(k => ({
...k,
hobby: 'swimming'
}));
// bad
function addPersions(persions) {
persions.push({
name: '吴',
age: 20
});
}
addPersions(persons);
// good
function addPersions(persions) {
return [...persions, {
name: '吴',
age: 20
}]
}
addPersions(persons);
const persons = [{name: 'liu', age: 18}, {name: 'wang', age: 22}];
// bad
function sortPersions(persions) {
// sort有副作用,会改变原数组
const sortArr = persions.sort((a, b) => b.age - a.age);
return sortArr;
}
sortPersions(persons);
// good
function sortPersions(persions) {
// sort有副作用,会改变原数组
const sortArr = [...persions].sort((a, b) => b.age - a.age);
return sortArr;
}
sortPersions(persons);
// very bad
const prev = { coffee: 1 };
const next = Object.assign(prev, { pizza: 42 });
// bad
const prev = { coffee: 1 };
const next = Object.assign({}, prev, { pizza: 42 });
// good
const prev = { coffee: 1 };
const next = { ...prev, pizza: 42 };
禁止不必要的条件
// bad
const hasValue = value !== NONE ? true : false;
const hasProducts = products.length > 0 ? true : false;
// good
const hasValue = value !== NONE;
const hasProducts = products.length > 0;
// bad
const hasValue = value ? true : false;
const hasProducts = products.length ? true : false;
// good
const hasValue = Boolean(value);
// bad
const hasProducts = products && Array.isArray(products);
// good
const hasProducts = Array.isArray(products);
// bad
function 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;
}
}
// good
function IsNetscapeOnSolaris() {
const { userAgent } = window.navigator;
return (
userAgent.includes('Mozilla') &&
userAgent.includes('SunOS') &&
!userAgent.includes('compatible')
);
}
// bad
if (errorMessage) {
log(LOG_LEVEL.ERROR, errorMessage);
} else {
log(LOG_LEVEL.ERROR, DEFAULT_ERROR_MESSAGE);
}
// good
log(LOG_LEVEL.ERROR, errorMessage || DEFAULT_ERROR_MESSAGE);
// bad
function 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
}));
}
// bad
const 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()
];
// good
const 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;
});
提前返回错误
// bad
function 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判断优化
// bad
if (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;
// good
const 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>;
// good
const 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 good
const 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>;
合理使用三元运算
// bad
let drink;
if (caffeineLevel < 50) {
drink = DRINK_COFFEE;
} else {
drink = DRINK_WATER;
}
// good
const drink = caffeineLevel < 50 ? DRINK_COFFEE : DRINK_WATER;
// bad
function 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>
}
// good
function 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 = 42
var 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);
}
}
// 储存状态的变量,初始值是 pending
status = 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,应该直接resolve
if (x === null) {
return resolve(x);
}
let then;
try {
// 把 x.then 赋值给 then
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return 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 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量called
if (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 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(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.resolve
this.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 + 44
console.log('res1');
return new MyPromise((res2) => setTimeout(() => res2(res + '11'), 1000));
// return new MyPromise((res2) => res2(res + '11'));
}).then((res) => {
// return res + 44
console.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. 方式二
```javascript
function 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); // 1
var 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)
装饰器
@testDec
class 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 1024
const 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.documentElement
if (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 date
if (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 Sunday
if (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.byteLength
const blockNum = size / blockSize
for (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 = null
store.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 = null
if (!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 * 1024
const 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 += 1
let 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.gcid
gcidWorker = null
resolve(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 err
return 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 gcid
const 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 = uploadType
console.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 = xlUploadCache
const [err, createRes] = await promiseCreateFile(createParmas)
if (err) {
console.log(err)
progress(constants.UPLOAD_FAIL)
return
}
if (!createRes.file) return createRes
if (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.params
clientObj.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) : null
if (val === name) return true
return 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 = type
const 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 = uploadType
const [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 = xlUploadCache
if (!xlUploadCache) {
clientObj = createRes.resumable.params
clientObj.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" />
<meta
name="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, //最大值,默认100
value: 0, //初始值,默认为0
range: 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';
//间隔值不能小于 1
if (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>