Git github 更多完整手写JS,之后也会以文章的形式稍后更新
防抖,节流
防抖为事件触发后N秒后执行回调,如果在N秒内再次触发,则重新计数,类似于百度的搜索效果
debounce
// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
// 缓存一个定时器id
let timer = 0
// 这里返回的函数是每次用户实际调用的防抖函数
// 如果已经设定过定时器了就清空上一次的定时器
// 开始一个新的定时器,延迟执行用户传入的方法
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
防抖,是多次执行变为最后一次的执行。而节流则为多次执行变为周期性执行
节流函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效
throttle
// func是用户传入需要防抖的函数
// wait是等待时间
const throttle = (func, wait = 50) => {
// 上一次执行该函数的时间
let lastTime = 0
return function(...args) {
// 当前时间
let now = +new Date()
// 将当前时间和上一次执行函数时间对比
// 如果差值大于设置的等待时间就执行函数
if (now - lastTime > wait) {
lastTime = now
func.apply(this, args)
}
}
}
setInterval(
throttle(() => {
console.log(1)
}, 500),
1
)
手撕instanceof
- 利用的是原型链的原理
- 首先获取当前类的原型,当前实例对象的原型链
- 而后利用原型链的原理,一直循环
- 取得当前对象得原型链得原型链
- 如果等于 对象得prototype 则返回True
- 否则如果等于null (Object基类得原型为null) 则返回false
function myinstanceof(example,classFunc){
let proto = Object.getPrototypeOf(example)
while(true){
if (proto === null){
return false
}
if (proto === classFunc.prototype){
return true
}
proto = Object.getPrototypeOf(proto)
}
}
new
- 首先明确new操作符所做的事情
- 创建了一个新的对象
- 而后使得新对象的__proto__ 指向构造函数的原型 (prototype)
- 调用构造函数,使用call,apply 改变this的指向
- 返回值为Object类型的对象,若不是,则返回全新的对象
function mynew(e,...args){
const demo = Object.create(e.prototype)
let fn = e.call(demo,...args)
return typeof fn === 'object' ? fn:demo
}
function A(){
}
const a = mynew(A)
console.dir(a)
console.log(a.__proto__ === A.prototype)
实现一个call
call做了什么
- 将函数设为对象的属性
- 执行或删这个函数
- 指定this到函数,并传入给定参数执行函数
- 如果不传入参数,则默认指定的是window
// 模拟 call bar.mycall(null);
//实现一个call方法:
// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()
Function.prototype.myCall = function(context = window, ...args) {
// this-->func context--> obj args--> 传递过来的参数
// 在context上加一个唯一值不影响context上的属性
let key = Symbol('key')
context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的方法
// let args = [...arguments].slice(1) //第一个参数为obj所以删除,伪数组转为数组
let result = context[key](...args);
delete context[key]; // 不删除会导致context属性越来越多
return result;
};
使用
//用法:f.call(obj,arg1)
function f(a,b){
console.log(a+b)
console.log(this.name)
}
let obj={
name:1
}
f.myCall(obj,1,2) //否则this指向window
模拟Object.create
// function mycreate(proto){
// function F(){}
// F.prototype = proto
// return new F()
// }
function mycreate(proto){
function F(){
}
F.prototype = proto
return new F();
}
function f(){}
fobj = mycreate(f)
console.log(fobj.__proto__ === f.prototype)
手写Promise 代码太长了。。。
解析URL
url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'
function parseParem(url){
let arr = url.split('?')[1].split('&')
let res = {}
for(let fn of arr){
let [key,value] = fn.split('=')
if (value === undefined){
res[key] = true
}
else{
if (key in res){
Array.isArray(res[key]) ? res[key].push(value) : res[key] = [res[key]].concat(value)
}
else{
res[key] = decodeURI(value)
}
}
}
return res
}
console.log(parseParem(url))
模板编译器的实现
正则匹配 test exec(顺序匹配,其结果挺有意思的) replace
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
console.log(render(template, data));
function render(template,data){
const reg = /\{\{(\w+)\}\}/;
if(reg.test(template)){
const name = reg.exec(template)[1]
console.log(reg.exec(template))
template = template.replace(reg,data[name])
return render(template,data)
}
return template
}
驼峰命名法的转换
正则呀,重点
var s1 = "get-element-by-id"
function change(fn){
return fn.replace(/-\w/g,function(x){
console.log(x)
return x.slice(1).toUpperCase()
})
}
console.log(change(s1))
-e
-b
-i
getElementById
查找字符串中出现最多的字符和个数
let str = "abcabcabcbbccccc";
let num = 0;
let char = '';
str = str.split('').sort().join('');
let res = new Map()
for (let s of str){
if (s in res){
res[s]+=1
}
else{
res[s] = 1
}
if (res[s] > num){
num = res[s]
char = s
}
}
// let re = /(\w)\1+/g;
// str.replace(re,($0,$1) => {
// console.log($0,$1)
// if (num < $0.length){
// num = $0.length
// char = $1;
// }
// });
console.log(`字符最多的是${num},出现了${char}`)
字符串查找
若存在,则返回首字母的下标
- js循环的下标为字符串
int -> str value.toString()
str -> int parseInt(str) parseFloat(str)
~~ 也能转
// a='34';b='1234567'; // 返回 2
// a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
console.log(isContain(a,b));
function isContain(a,b){
for (let i in b){
if(a[0] === b[i]){
let tmp = true
for (let j in a){
console.log(typeof i)
if (a[j] !== b[~~i + ~~j]){
console.log(a[j],b[i+j])
tmp = false
}
}
if (tmp){
return i
}
}
}
return -1
}
插播知识点 JSON.parse() 从字符串中解析JSON对象 JSON.stringify 将对象解析为字符串
正则匹配 ,永远的晕头转向
- 邮箱
- 身份证
- 电话
- 数字的千分位转换
代码稍后会在Github,或者是新文章内更新
实现数组的flat
- 多维数组转一维
let arr = [1, [2, [3, [4, 5]]], 6];
// let str = JSON.stringify(ary);
// console.log(str)
arr_flat = arr.flat(Infinity);
console.log(arr_flat)
let res = []
const fn = function(arr){
for(let i = 0; i<arr.length; i++){
let tmp = arr[i]
if (Array.isArray(tmp)){
fn(tmp)
}
else{
res.push(tmp)
}
}
}
fn(arr)
console.log(res)
function flatten(arr){
return arr.reduce((pre,cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur)
},[])
}
console.log(flatten(arr))
- 对象的扁平化与反扁平化
扁平化
function objectFlat(obj = ''){
const res = {}
function flat(item , preKey = ''){
Object.entries(item).forEach(([key,value]) => {
let newKey = key
// console.log(value,typeof value,Array.isArray(value))
if (Array.isArray(item)){
// console.log('是数组')
newKey = preKey ? `${preKey}[${key}]` : key
}else{
newKey = preKey ? `${preKey}.${key}` : key
}
if (value && typeof value === 'object'){
flat(value , newKey)
}else{
res[newKey] = value
}
})
}
flat(obj)
return res
}
const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
console.log(objectFlat(source));
const obj = {
a: 1,
b: [1, 2, { c: true }],
c: { e: 2, f: 3 },
g: null,
};
console.log(objectFlat(obj));
反扁平化
function testPropTypes(value, type, dev) {
const sEnums = ['number', 'string', 'boolean', 'undefined', 'function']; // NaN
const oEnums = ['Null', 'Object', 'Array', 'Date', 'RegExp', 'Error'];
const nEnums = [
'[object Number]',
'[object String]',
'[object Boolean]',
'[object Undefined]',
'[object Function]',
'[object Null]',
'[object Object]',
'[object Array]',
'[object Date]',
'[object RegExp]',
'[object Error]',
];
const reg = new RegExp('\\[object (.*?)\\]');
// 完全匹配模式,type应该传递类似格式[object Window] [object HTMLDocument] ...
if (reg.test(type)) {
// 排除nEnums的12种
if (~nEnums.indexOf(type)) {
if (dev === true) {
console.warn(value, 'The parameter type belongs to one of 12 types:number string boolean undefined Null Object Array Date RegExp function Error NaN');
}
}
if (Object.prototype.toString.call(value) === type) {
return true;
}
return false;
}
}
function getValue(obj, key, defaultValue) {
// 结果变量
const defaultResult = defaultValue === undefined ? undefined : defaultValue;
if (testPropTypes(obj, 'Object') === false && testPropTypes(obj, 'Array') === false) {
return defaultResult;
}
// 结果变量,暂时指向obj持有的引用,后续将可能被不断的修改
let result = obj;
// 得到知道值
try {
// 解析属性层次序列
const keyArr = key.split('.');
// console.log(keyArr[0])
var res = []
for (let i = 0; i < keyArr.length; i++) {
let k0 = keyArr[i]
// console.log(k0.split('['))
k0 = k0.split('[')
for (let i = 0; i < k0.length; i++) {
if (k0[i] !== '') {
res.push(k0[i][0])
}
}
}
console.log(res)
// 迭代obj对象属性
for (let i = 0; i < res.length; i++) {
// 如果第 i 层属性存在对应的值则迭代该属性值
if (result[res[i]] !== undefined) {
result = result[res[i]];
// 如果不存在则返回未定义
} else {
return defaultResult;
}
}
} catch (e) {
return defaultResult;
}
// 返回获取的结果
return result;
}
// 示例
var object = { a: [{ b: { c: 3 } }] }; // path: 'a[0].b.c'
var array = [{ a: { b: [1] } }]; // path: '[0].a.b[0]'r
// res = 'a'
// function getValue(target, valuePath, defaultValue) {}
console.log(getValue(object, "a[0].b.c", 0)); // 输出3
console.log(getValue(array, "[0].a.b[0]", 12)); // 输出 1
console.log(getValue(array, "[0].a.b[0].c", 12)); // 输出 12
什么是柯里化
将接受多个参数的函数,变为接受部分参数的函数。而后返回一个函数接受余下参数。足够后,执行函数
function curry(fn,args){
var length = fn.length
var args = args || []
return function(){
// Array.prototype.slice.call(arguments) 非数组对象转为数组
newArgs = args.concat(Array.prototype.slice.call(arguments))
if(newArgs.length < length){
return curry.call(this,fn,newArgs)
}else{
return fn.apply(this,newArgs)
}
}
}
function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
console.log(multi(2,3)(4))
利用闭包实现结果缓存(备忘模式)
function memorize(fn){
var cache = {}
return function(){
// 将类数组转化为数组
// arguments 为ES5写法的参数的总和
// 而现在使用...args 解构赋值
console.log(arguments)
const args = Array.prototype.slice.call(arguments)
const key = JSON.stringify(args)
return cache[key] || (cache[key] = fn.apply(fn,args))
}
}
function add(a) {
return a + 1
}
var adder = memorize(add)
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(2)
function memorize(fn){
var cache = {}
return function(...args){
// 将类数组转化为数组
// arguments 为ES5写法的参数的总和
// 而现在使用...args 解构赋值
console.log(args)
console.log(arguments)
const key = JSON.stringify(args)
return cache[key] || (cache[key] = fn.apply(fn,args))
}
}
function add(a) {
return a + 1
}
var adder = memorize(add)
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(1) // 输出: 2 当前: cache: { '[1]': 2 }
adder(2)
[Arguments] { '0': 1 }
[ 1 ]
[Arguments] { '0': 1 }
[ 2 ]
[Arguments] { '0': 2 }
arguments 的用法以及数据结构,以及解构赋值
更多代码稍后会在Github,或者是新文章内更新