call: 简单来说就是改变执行方法当前的this,可以传入不定参数
Function.prototype.myCall = function(context, ...args) {
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
}
getMessage.myCall(obj, 'name');
立即执行getMessage方法,不过是以obj.getMessage的方式,
所以这个时候getMessage内的this是obj,传入参数'name'。
(obj可能压根就没有getMessage方法)
apply: 和call作用一致,传递参数格式不同,需是数组
Function.prototype.myApply = function(context, args) {
context.fn = this;
const result = context.fn(args);
delete context.fn;
return result;
}
getMessage.myApply(obj, ['name']);
bind: 改变指定方法的this后执行,以函数的形式返回执行结果
Function.prototype.myBind = function(context, ...args) {
return () => {
return this.myApply(context, args);
}
}
const result = getMessage.myBind(obj)
result是一个函数,再执行一次才是getMessage方法的执行结果
new:将构造函数实例化,将参数创建为对象以及赋值原型方法
function createNew(Ctor, ...args) {
const obj = Object.create(Ctor.prototype);
const ret = Ctur.apply(obj, args);
return ret instanceof Object ? ret : obj;
}
1. 将构造函数的原型赋值给新建的obj的隐式原型__proto__。
2. 在obj下执行构造函数,并传入参数,
这个时候构造函数内的this就是obj。
3. 如果这个'构造函数'没有return对象格式的结果,
返回新创建的obj。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function() {
console.log(this.name);
}
const xm = createNew(Person, 'xiaoming', 22);
instanceof: 判断一个变量是否是某个类型
function myInstanceOf(left, right) {
while(1) {
if(left.__proto__ === null) {
return false;
}
if(left.__proto__ === right.prototype) {
return true;
}
left = left.__proto__;
}
}
instanceof的原理就是通过原型链查找,
所以一直向上查找左侧的隐式原型__ptoto__是否等于右侧显式原型,
原型链的尽头是null,没找到就返回false。
myInstanceOf([1,2], Array); // true
forEach: 遍历数组,这个大家经常用,想必不说都懂
Array.prototype.myForEach = function(fn) {
const arr = this;
for(let i = 0; i < arr.length; i++) {
fn(arr[i], i, arr);
}
}
接受一个fn回调函数,传递给回调函数三个参数:
每项的值,下标,自身。第二个参数有人用么?
const arr = ['a','b','c'];
arr.myForEach(item => {
console.log(item); // a b c
})
map: 返回经过处理的数组
Array.prototype.myMap = function(fn) {
const arr = this;
const ret = [];
for(let i = 0; i < arr.length; i++) {
ret.push(fn(arr[i], i, arr));
}
return ret;
}
和forEach类似也是接受一个fn回调,
不过会将回调处理的结果放入一个新的数组,
所以map回调内的每一项需要return,
因为要组成新的数组结果。
const arr = ['a', 'b', 'c'];
const newArr = arr.myMap(item => { // a1 b1 c1
return item + 1; // 要return结果
})
filter: 返回回调处理结果为true的新数组
Array.prototype.myFilter = function(fn) {
const arr = this;
const ret = [];
for(let i = 0; i < arr.length; i++) {
if(fn(arr[i], i, arr)) {
ret.push(arr[i]);
}
}
return ret;
}
大同小异,过滤出处理条件为true的值。
返回数组中不重复的值:
function repeat(arr) {
return arr.myFilter((v, i, a) => {
return a.indexOf(v) === a.lastIndexOf(v);
})
}
const arr = [1,2,3,4,1,2,3,5,6,8,3];
repeat(arr); // [4,5,6,8]
find:返回处理条件第一个为true的数组项
Array.prototype.myFind = function(fn) {
const arr =this;
for(let i = 0; i < arr.length; i++) {
if(fn(arr[i], i, arr)) {
return arr[i];
}
}
}
否则返回undefined
findIndex: 返回处理条件第一个为true的数组下标
大家自己写下咯~
every:如果数组每一项都符合处理条件。返回true,否则返回false
Array.prototype.myEvery = function(fn) {
const arr = this;
for(let i = 0; i < arr.length; i++) {
if(!fn(arr[i], i, arr)) {
return false;
}
}
return true;
}
some:只要数组有一项符合处理条件。返回true,都不满足返回false。
这个相信大家都知道怎么写了~
reduce: 一般为数组做累计结果使用。
Array.prototype.myReduce = function(fn, second) {
const arr = this;
let index = 0;
if(typeof second === undefined) { // 没传第二个参数
index = 1;
second = arr[0];
}
for(let i = index; i < arr.length; i++) {
const invoked = fn(second, arr[i], i, arr);
second = invoked;
}
return second;
}
一般会传入第二个参数作为初始值,如果没有传入,
初始值就是数组的第一项,将处理的结果进行累计,
最后返回累计的结果。
返回数组中指定参数重复的次数:
function count(arr, value) {
return arr.myReduce((f, s) => {
return Object.is(s, value) ? f + 1 : f + 0;
}, 0)
}
const arr = [1,2,3,4,1,2,3,2,1];
count(arr, 2); // 3
debounce: 函数防抖
function debounce(fn, delay = 1000) {
let timer;
return () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arguments);
}, delay)
}
}
函数防抖指的是一定时间内没有再次触发函数,就执行该函数,否则重新计时。
wow为例:
2.5s施法的寒冰箭,再读条的过程中,
你身子抖动打断了施法,想再次触发技能时麻烦您重新读条。
throttle:函数节流
function throttle(fn, delay = 100) {
let timer;
return () => {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay)
}
}
}
函数节流指的是规定某个时间内只能执行一次函数。
wow为例:
火冲为瞬发技能,不过你规定cd为8s,
所以即使8s内按了10次,也只能来1发,节省点体力吧。
deepClone:深拷贝
一般够用型
function deepClone(source) {
if (typeof source !== 'object' || source == null) {
return source;
}
const target = Array.isArray(source) ? [] : {};
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (typeof source[key] === 'object' && source[key] !== null) {
target[key] = deepClone(source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
解决循环引用和symblo类型
function cloneDeep(source, hash = new WeakMap()) {
if (typeof source !== 'object' || source === null) {
return source;
}
if (hash.has(source)) {
return hash.get(source);
}
const ret = Array.isArray(source) ? [] : {};
Reflect.ownKeys(source).forEach(key => {
const val = source[key];
if (typeof val === 'object' && val != null) {
ret[key] = cloneDeep(val, hash);
} else {
ret[key] = val;
}
})
return ret;
}
Promise:手写简易版
class MyPromise {
constructor(fn) {
this.state = 'PENDING';
this.value = null
this.resolvedCallbacks = []
this.rejectedCallbacks = []
const resolve = value => {
if (this.state === 'PENDING') {
this.state = 'RESOLVED'
this.value = value
this.resolvedCallbacks.map(cb => cb())
}
}
const reject = value => {
if (this.state === 'PENDING') {
this.state = 'REJECTED'
this.value = value
this.rejectedCallbacks.map(cb => cb())
}
}
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }
if (this.state === 'PENDING') {
this.resolvedCallbacks.push(onFulfilled)
this.rejectedCallbacks.push(onRejected)
}
if (this.state === 'RESOLVED') {
onFulfilled(this.value)
}
if (this.state === 'REJECTED') {
onRejected(this.value)
}
}
catch(fn) {
return this.then(null, fn);
}
}
const promise = new MyPromise((resolve, reject) => {
resolve('hello world~');
})
promise.then(res => {
console.log(res);
})
iterator:不使用Generator
函数创建迭代器。
function myIterator(items) {
let i = 0;
return {
next() {
const done = i >= items.length;
const value = !done ? items[i++] : undefined;
return {
done, // 是否全部迭代完成
value // 返回迭代的值
}
}
}
}
const interator = myIterator([1, 2, 3]);
interator.next();
JSON.stringify: 将对象转为json字符串
function jsonStringify(obj) {
const type = typeof obj;
if (type !== 'object') {
if (type === 'string') {
obj = '"' + obj + '"';
}
return String(obj);
} else {
const json = [];
const arr = Array.isArray(obj);
for (const k in obj) {
let v = obj[k];
const type = typeof v;
if (type === 'string') {
v = '"' + v + '"';
} else if (v === null) { // 处理null情况
v = null
} else if (/function|undefined/.test(type)) {
// 原生方法会移除function和undefined,其实我们可以不移除
delete obj[k];
} else {
v = jsonStringify(v); // 递归
}
json.push((arr ? "" : '"' + k + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
}
}
const obj = {
a: 'a1',
b: [1, 2, 3],
c: 22,
d: function () {},
e: Date.now(),
f: null,
g: /str/ig,
h: undefined
}
const str = jsonStringify(obj);
// {"a":"a1","b":[1,2,3],"c":22,"e":1562815128952,"f":null,"g":{}}
JSON.parse: 将字符串格式的对象转为对象。
function jsonParse(str) {
return new Function('return ' + str)(); // return后有一个空格
}
很神奇有木有,直接在字符串前面加上'return '关键字就可以转为对象。
在组件精讲小册里有一个实例,在线vue单文件编辑器。
原理就是将编辑器内的vue单文件字符串使用正则分割,
js部分将‘export default’替换为'return '。
通过new Function转为js对象使用。
const sum = new Function('a','b','return a + b');
sum(1, 2); // 3
const str = '{"a":"a1","b":[1,2,3],"c":22,"e":1562815128952,"f":null,"g":{}}';
jsonParse(str); //
a: "a1",
b: [1, 2, 3],
c: 22,
e: 1562815128952,
f: null,
g: {}
Events:事件中心管理
class Events {
constructor() {
this._evnets = Object.create(null);
}
on(event, fn) { // 往事件中心添加事件
if (Array.isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.on(evnet[i], fn);
}
} else {
(this._evnets[event] || (this._evnets[event] = [])).push(fn);
}
}
emit(event, ...args) { // 触发事件中心对应事件
const cbs = this._evnets[event];
if (cbs) {
for (let i = 0; i < cbs.length; i++) {
cbs[i].apply(this, args);
}
}
}
off(event, fn) { // 移除事件
if (!arguments) {
this._evnets = Object.create(null);
return this;
}
if (Array.isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.off(event[i], fn);
}
return this;
}
if (!fn) {
this._evnets[event] = null;
return this;
}
const cbs = this._evnets[event];
let i = cbs.length;
while (i--) {
const cb = cbs[i];
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break;
}
}
return this;
}
once(evnet, fn) { // 只执行一次
function on() {
this.off(evnet, on);
fn.apply(this, arguments);
}
on.fn = fn;
this.on(evnet, on);
return this;
}
}
const event = new Event();
event.on('test', (name, sex) => { // 添加事件
console.log(`${name}:${sex}`);
})
event.emit('test', 'cc', 'man'); // 传参并触发事件
evnet.off(); // 清空所有事件
setInterval: 使用setTimeout模拟,并可以取消
function mySetInterval(fn, delay) {
let timer;
const loop = (fn, delay) => {
timer = setTimeout(() => {
loop(fn, delay);
fn.call(this, timer);
}, delay);
};
loop(fn, delay);
}
mySetInterval(timer => {
console.log('test');
// clearTimeout(timer); 取消定时器
}, 200);
setInterval: 使用requestAnimationFrame模拟
function mySetInterval(fn, interval) {
const now = Date.now;
let startTime = now();
const loop = () => {
const timer = requestAnimationFrame(loop);
if (now() - startTime >= interval) {
startTime = now();
fn.call(this, timer);
}
}
loop();
}
一般来说是不建议使用setInterval的,
如内部函数复杂就不能保证一定在规定时间内自动执行。
一般是通过setTimeout模仿setInterval。
那为什么要实现setInterval?
因为它内部的实现是使用requestAnimationFrame实现的,
该方法自带函数节流。
如有持续的动画需要执行,
基本会保证在16.6毫秒内执行一次,
提高动画性能并延时也是精确的。
mySetInterval(timer => {
console.log('a');
// cancelAnimationFram(timer); 取消当前定时器
})
setTimeout: 使用requestAnimationFrame模拟
定时执行一次回调就取消掉,自己实现下吧~
分享一个笔者自己写的组件库,哪天可能会用的上了 ~ ↓
你可能会用的上的一个vue功能组件库,持续完善中...