Javascript

240 阅读10分钟

实现拖拽

dragable

dataTransfer {setData  getData}

dragStart   

dragEnd

dragover   e.preventDefault();

drop   e.target.appendChild

默认地,无法将数据/元素放置到其他元素中。如果需要设置允许放置,我们必须阻止对元素的默认处理方式。

需要通过调用 ondragover 事件的 event.preventDefault() 方法:

//drop放置事件
document.addEventListener("drop", function(event) {
    event.preventDefault();
    if (event.target.className == "droptarget") {
        document.getElementById("demo").style.color = "";
        event.target.style.border = "";
        var data = event.dataTransfer.getData("Text");
        //ondrop中可以获取到dataTransfer的数据
        event.target.appendChild(document.getElementById(data));
    }
});

es5手写实现系列

new

要点:

1.如果构造函数本身有return,且为引用类型值则返回该return

2.else1,返回一个对象,带构造函数的参数

const mynew = function(fn,...args){
    const obj = Object.create(fn.prototype); //__proto__=fn.prototype
    const res = fn.apply(obj, args); //array object function直接返回
    return typeof res === 'object' ? res || obj;
}

L instanceof R

要点:

1.R的原型是否在L的原型链(的原型链...直到最顶层的原型链)上

2.返回true / false

//递归模式
const myInstanceof = (l, r) => {
    const pro = r.prototype;
    l = l.__proto__;

    return function t(){
        if(l === null){
            return false;
        }
        if(l === pro){
            return true;
        }
        l = l.__proto__;
        return t();
    }
}
//while 循环模式
const myInstanceof = function(l, r){
    const o = r.prototype;
    l = l.__proto__;
    while(true){
        if(l === null){return false;}
        if(l === o){return true;}
        l = l.__proto__;
    }
}

bind手写  apply call

要点:

1.第一个参数为改变this指向

2.bind(fn,...args)返回一个函数gn,args.concat((gn)的arguments)

// mybind(fn,object[,...args])
const mybind = function() {
    const args = [].slice.call(arguments);
    const fn = args[0];
    const _this = args[1];
    const other = args.slice(2);
    return function(){
        const newargs = [].slice.call(arguments);
        fn.apply(_this,other.concat(newargs));
    }
}

函数表达式、函数声明

//具名函数表达式fn只有在表达式内部可以访问
//且fn不能被修改,默认忽略
//函数表达式不会提升,函数声明会被提升
console.log(foo); //undefined
console.log(foo2); //f foo2(){...}
var foo = function fn(){
    fn=12;
    console.log(fn);
}
foo(); //f fn(){...}
console.log(fn); //fn is not defined

function foo2(){
    console.log(2);
}

作用域: es5没有块级作用域,只有函数作用域和全局作用域。es6中引入的let const 关键字生成块级作用域。

if(!a in window){
    var a=12; //var 变量提升
}
console.log(a); //undefined

//等价于
var a;
if(!a in Object.keys(window)){
   a=12
}
console.log(a); //undefined
//for循环与任务队列
for(var i=0;i<5;i++){
    setTimeout(()=>{console.log(i);},0)
} //55555
console.log(i); //5
//如何打印出0~4?
//以下:

//使用IIFE闭包存储每次的i值
for(var i=0;i<5;i++){
    (function(j){
        setTimeout(()=>{console.log(i);},0)
    })(i)
} //0,1,2,3,4

//let const 块级作用域每次i都会存储一次
for(let i=0;i<5;i++){
    setTimeout(()=>{console.log(i);},0)
} //0,1,2,3,4

array的常用方法

var arr = new Array(23,12,2,3);

array数组首尾添加和删除

.pop()  1.返回pop得item

.push(arg1[, arg2[, arg3...]])  1.返回数组push后得长度

.shift()  1.返回数组push后得长度

.unshift(arg1[, arg2[, arg3...]])  1.返回数组push后得长度

.slice(from [, to])  裁剪  1.to默认为到length 2.from<=a<to 3.不改变原数组

.splice(from [,to [,replace] ])  裁剪并替换     1.to默认为length 2.replace没有则不做替换 3.改变原数组

.forEach .map for in  遍历

.join   转换成字符串

.reduce((total,curr)=>{  }, initValue)   将列表各项经过一定得处理返回一个处理后得结果

未完待续...

Object常用属性

.create(obj)  1.obj可为null  2.将新object.__proto__=obj

.assign(obj, other)  1.将other合并替换obj相同属性

.keys(obj)  1.返回obj的所有iteratorable的属性值

.defineProperty(obj, { keys1 in obj : {handler} })  1.为obj重的属性定制默认行为    2.set get必须同时定制

.getOwnPropertyNames

.getOwnPropertyKeys

.getOwnPropertyDescriptor

.frezze

.isFrozen

.seal

.isSealed

.preventExtensions

.isExtensible

未完待续...

修改object属性值的方法

Object.defineProperty(obj,prop,handler)

Proxy(obj,handler)

Object.defineProperties(obj, {prop: handler})

//分别使用Object.defineProperty/proxy实现一个简易的数据双向绑定//v-model

  
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Document</title>
</head>
<body>
  <!-- V  -->
  <input type="text" id="txt" />
  <script>
</body>
</html>




    let obj = {};
    let data = '';
    // V => M 【视图层到模型层的数据绑定】            
    let ipt = document.querySelector('#txt')    
    ipt.oninput = function () {      
        // console.log(this.value)                  
        obj.name = this.value;
        obj2.name = this.value;
    }    
    // M  ==> V 【模型层到视图层的数据绑定】           
    Object.defineProperties(obj, {
        name: {
            set(newVal) {
                console.log('set  this: ', this);
                data = newVal;
                ipt.value = newVal;
                //改变视图层数据
            }, 
            get() { 
                return data; 
            }
        }
    })
    //M => V  proxy实现数据模型到视图模型的数据绑定    
    var obj2 = new Proxy(obj, {
        set(tar, prop, value) {
            tar[prop] = value;
            ipt.value = value;
        }
    });

Reflect

在了解了Proxy之后,若需要在Proxy内部调用对象的默认行为,该如何实现?

Reflect正是ES6 为了操作对象而提供的新 API。

基本特点

  • 只要Proxy对象具有的代理方法,Reflect对象全部具有,以静态方法的形式存在。这些方法能够执行默认行为,无论Proxy怎么修改默认行为,总是可以通过Reflect对应的方法获取默认行为。
  • 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
  • Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

静态方法

Reflect对象一共有 13 个静态方法(匹配Proxy的13种拦截行为)。

  • Reflect.apply(target, thisArg, args)    Object默认的apply事件
  • Reflect.construct(target, args)    Object默认的构造函数
  • Reflect.get(target, name, receiver)  默认get行为
  • Reflect.set(target, name, value, receiver)   set行为
  • Reflect.defineProperty(target, name, desc)   定义对象属性行为
  • Reflect.deleteProperty(target, name)   delete obj.name行为
  • Reflect.has(target, name)  判断keys in obj 
  • Reflect.ownKeys(target)  Object.getOwnPropertyNames()+Object.getOwnPropertySymbols 获取非原型链上的属性值
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

大部分与Object对象的同名方法的作用都是相同的,而且它与Proxy对象的方法是一一对应的。

es6新增数据类型

map set   constructor

weakmap,weakset    引用为弱引用;不能存储Symbol; constructor

// Map new Map([[key1,value1][,[...]]])
var mp = new map([['key','value'],['key2','value2']]);
const key = {12:23};
mp.set(key,34); //设置objectkey对应的值
mp.get(key); //获取objkey对应的值
mp.keys(); //Iterator
mp.values(); //Iterator

// Set newSet([ar1[,arg...]])
var st = new Set([arr1...]);
st.add(12);
st.delete(arr1);
st.keys(); //Iterator
st.values(); //Iterator
st.has(arr1); //判断某个元素是否在集合里

//iterator 迭代器 function Iterator(object[, keyof])
var it = Iterator({12:23})
it.next()  //{value:{},done:true/false}
it.next().value===undefined  it.next().done===true //迭代器迭代结束
for(var [key, value] in it){console.log(key,value);} 

Symbol 唯一值 function

var sy = Symbol(12);
var sy2 = Symbol(12);
sy===sy2 //false
mp.set(12,sy)
mp.keys //Iterator({0:12}) Symbol不可被迭代
Object.getOwnPropertySymbols()

每次定义都会生成一个独一无二的值

eg:

Symbol(1) === symbol(1)  //false

可以类型转换不可以加入运算

const b = Symbol(1);

Boolean(b) //true
!!b //false
b.toString() //Symbol(1)
b+1 //Uncaught TypeError: Cannot convert a Symbol value to a number

不可枚举

用来设置对象的属性值时,Object.keys、Object.Stringfy、Object.getOwnProperties无法遍历出Symbol属性和对应的属性值,可以使用Object.getOwnPropertySymbols Reflect.ownKeys

eg:

const a = Symbol(1);   
const b = {[a]: 12}; 
console.log('Object.getOwnPropertyNames(b): ', Object.getOwnPropertyNames(b)); //[]consle.log(b[a]);  //12    ||  conosle.log(b[ObjectGetOwnPropertySymbols(b)[0]]); //12

Symbol.for

参数必须为字符串,先检查有没有使用该字符串调用 Symbol.for 方法创建的 symbol 值,如果有,返回该值,如果没有,则使用该字符串新创建一个。该方法创建 symbol 值后会在全局范围进行注册。

页面中的iframe socket worker为同一个全局范围

const bsf = Symbol.for('1');console.log("bsf===Symbol.for('1'): ", bsf === Symbol.for('1')); //true

Symbol.keyFor

传入一个 symbol 值,返回该值在全局注册的键名

const bsf = Symbol.for('1');console.log('Symbol.keyFor(bsf): ', Symbol.keyFor(bsf)); //'1'

generator iterator async/await

function*(){
    yield 'value';
}

//iterator
[...'string'] //s t r i n g
[a, b, c] = new Set(["a", "b", "c"]);
a // "a"

async function (){
    a = await new Promise(resolve=>{ resolve('resolve a') });
    b = await new Promise(resolve=>{ resolve('resolve b'+a) });
    console.log(b); 
}

//generator
function* a(){ 
    yield new Promise(resolve=>{ resolve('resolve a') });
}
function* b(){ 
    yield new Promise(resolve=>{ resolve('resolve b' ) });
}
console.log(t().next().value.then(res=>{})+''+s().next().value.then(res=>{}));

async和await是generator的语法糖

手写系列 es6 es2021...

generator

1.generator的.next(arg) 会被赋值给yield前面的参数

2.yield执行出的表达式值放在 generator().next().value中

手写async/await

1.async会被替换成generator函数

2.await替换成yield   然后自动执行且await后面可以跟promise等异步

  cc = function (generator) {    
    let acc = generator(); //yield1    
    return new Promise((resolve) => {      
        let anext = acc.next();      
        function next(anext) {        
            console.log(anext);        
            const aPromise = Promise.resolve(anext.value);        
            aPromise.then((res) => {             
                if (anext.done === true) {            
                    resolve(res);          
                } else {            
                    const next2 = acc.next(res); //yield2 a=yield1赋值            
                    anext = next2;            
                    next(anext);          
                }        
            });      
        }      
        next(anext);})  
    }

深拷贝

/** * 判断是否是基本数据类型 * @param value  */ 
function isPrimitive(value){  
    return (typeof value === 'string' ||   typeof value === 'number' ||   typeof value === 'symbol' ||  typeof value === 'boolean')
}/** * 判断是否是一个js对象 * @param value  */
function isObject(value){  
    return Object.prototype.toString.call(value) === "[object Object]"
}/** * 深拷贝一个值 * @param value  */
function cloneDeep(value){  
    // 记录被拷贝的值,避免循环引用的出现  
    let memo = {};  
    function baseClone(value){    
    let res;    // 如果是基本数据类型,则直接返回    
    if(isPrimitive(value)){      
        return value;    
        // 如果是引用数据类型,我们浅拷贝一个新值来代替原来的值    
    }else if(Array.isArray(value)){      
        res = [...value];    
    }else if(isObject(value)){      
        res = {...value};    
    }    
    // 检测我们浅拷贝的这个对象的属性值有没有是引用数据类型。如果是,则递归拷贝    
    Reflect.ownKeys(res).forEach(key=>{      
        if(typeof res[key] === "object" && res[key]!== null){        
        //此处我们用memo来记录已经被拷贝过的引用地址。以此来解决循环引用的问题        
        if(memo[res[key]]){          
            res[key] = memo[res[key]];        
        }else{          
            memo[res[key]] = res[key];          
            res[key] = baseClone(res[key])        
        }      
        }    
    })    
        return res;    
    }  
    return baseClone(value)
}

Promise手写

使用:new Promise((resolve, reject)=>{}).then((res)=>{}, (rej)=>{}).then((res2)=>{}, rej=>{})

1、Promise 有三种状态{PENDING: 'pending', FULFILLED: 'fulfilled', REJECTED: 'rejected'}

2、resolve 改变状态为fulfilled

3、reject改变状态为rejected

4、.then(resoledCallback, rejectedCallback)两个函数参数分别在状态不同时执行

5、.then().then()可以链式调用,即then返回一个新的Promise

6、.catch 是 .then(,rejectedCallback)的别名

...

    const state = { PENDING: 'pending', FULFILLED: 'fulfilled', REJECTED: 'rejected' };    
    class Promise {      
        constructor(executor) {        
            this.PromiseState = state.PENDING; //存放状态        
            this.PromiseResult = undefined; //存放正确结果        
            this.PromiseReson = undefined; //存放错误结果        
            this.onResolvedCallbacks = []; // 存放成功的回调的         
            this.onRejectedCallbacks = []; // 存放失败的回调的        
            const resolve = (res) => {          
                this.PromiseState = state.FULFILLED;          
                this.PromiseResult = res;  
                //依此执行resolvecallback
                onResolvedCallback.map(fn=>fn(res));      
            }        
            const reject = (rej) => {          
                this.PromiseState = state.REJECTED;          
                this.PromiseReson = rej;   
                //依次执行rejectcallback
                onRejectedCallbacks.map(err=>err(rej))     
            }        
            try {          
                executor(resolve, reject);        
            } catch (e) {          
                // 出错走失败逻辑          
                reject(e)        
            }      
        }      
        then = (onFullfilled, onRejected) => {        
            return new Promise((resolve, reject) => 
                {          
                    //状态是pending时 不执行 存入callback数组中等待
                    if (this.PromiseState === state.PENDING) {            
                        this.onResolvedCallbacks.push(() => {              
                            onFullfilled(this.PromiseResult)            
                        });            
                        this.onRejectedCallbacks.push(() => {              
                            onRejected(this.PromiseReson)            
                        });          
                    }         
                    //状态是fufilled时,执行onFulfilled函数 
                    if (this.PromiseState === state.FULFILLED) {            
                        try {              
                            let x = onFullfilled(this.PromiseResult);              
                            resolve(x);            
                        } catch (e) {              
                            onRejected(e);            
                        }          
                    }
                    //状态是reject时,执行onReject函数
                    if (this.PromiseState === state.REJECTED) {           
                        try {              
                            let x = onRejected(this.PromiseReson);              
                            resolve(x);            
                        } catch (e) {              
                            onRejected(e);            
                        }          
                    }        
              })      
         }    
     }

Promise.race

1、参数为promise数组

2、当数组中的任意一个promise的state不再是pending时则抛出此时的返回值

3、是reject时返回catch/then(resolved,rejected)

Promise.prototype.race = function(promiseArr) {
    promiseArr.map(acc => {
        acc.then(res => {
            Promise.resolve(res); 
        });
    });
}

Promise.any

1、参数为promise数组

2、返回promise数组中第一个resolve的值

3、当所有的promise都是reject时返回AggregateError

Promise.prototype.any = (promiseArr) => {
    const result=0;
    promiseArr.map(acc => {
        acc.then(res => {
            result++;
            Promise.resolve(res);
        }, err => {
            if(result===promiseArr){
                Promise.reject('AggregateError: All promises were rejected');
            }
        });
    })
}

Promise.all

1、参数为promise数组

2、promise全部resolve后返回顺序与输入promise一致的resolve返回值的数组,

3、当其中一项reject时返回rejected的reason

Promise.prototype.all = (promiseArg) => {
    const result = [];
    promiseArg.map((acc, index) => {
        acc.then(res => {
            result[index] = res;
            if(result.length === promiseArg.length) {
                Promise.resolve(result); 
            }
        }, err => {
            result.push(err);
            Promise.reject(result);
        })
    })
}

函数柯里化

1、函数为参数

2、返回一个函数,且可以将需要的参数一个一个传入并最重执行出结果

curry(add)(1)(2)

//函数柯里化
function curry(fn) {      
    const len = fn.length;      
    return function t() {        
        const args = [].slice.call(arguments);        
        if (args.length === len) {          
            fn.apply(null, args);          
            return 'apply';        
        }        
        return function () {          
            const args2 = [].slice.call(arguments);          
            return t.apply(null, args.concat(args2));        
        }      
    }    
}
//函数柯里化
function curry(fn, ...arg) {
  if (arg.length >= fn.length) {
    return fn.apply(this, arg);
  }
  return function (...arg2) {
    return curry(fn, ...arg, ...arg2);
  }
}

//调用
curry((a,b,c)=>{console.log(a+b+c)})(1)(2)(3);

//logs
//6
//"apply"

//实现 add(1)  1  add(1)(2)(3)...(n)
//console控制台中打印出add的结果
function add(...args){
    const _add = function(...args2){
        return add(...args, ...args2)
    }
    _add.toString=()=>{
        args.reduce((prev, cur)=> cur+prev)
    }
    return _add;
}

promise处理n并发

要点:

1.同时执行的请求控制在n

2.一个执行完将未执行的放入执行栈

//输入callbacks:[fetch]
const limitpromise = function(callbacks, maxnum){
    let result = 0; //执行完的数量
    let count = 0; //执行中数据
    const len =callbacks.length; //所有需要执行的数量

    return new Promise(function(resolve,reject){
        try{
            while(count<maxnum){
                next();
            }
            function next(){
                count++;
                if(count >= len && result === len){ resolve(); return true; }//全部执行完毕终止
                if(count-1<len){           
                    Promise.resolve(callbacks[count-1]()).then(res=>{ 
                        result++; if(count-1<len) { next(); };
                    },err=>{
                        result++; if(count-1<len){ next(); };
                    });
                }
            }
        } catch(err) {reject(err)};
    })
}

状态管理 

redux、flux、vuex、mobx原理

1、redux在flux的理论基础上做了改进其中包括:1全局只有一个store   2state为只读,每次都用一个新的state替换旧的state(reducer是个纯函数)

2、vuex在redux的基础上做了改进,1vuex时专门针对vue的库,由于vue视图自动更新所以不需要订阅store到视图的更新   2vuex用mutation替换了redux的reducer,允许直接修改state,且将state修改的过程更加可视化(摈弃了switch case)

3、mobx可以说是react中的vuex,响应式数据存储,奉行:当数据发生变化时其相关视图自动更新

手动实现一个redux

1、dispatch: 触发action

2、reducer: 处理action和返回无副作用的state

3、subscribe: 订阅store的变化,一旦store发生了变化,传入的回调函数就会被调用

redux案例

import { createStore } from 'redux';

const initState = {
  milk: 0
};

function reducer(state = initState, action) {
  switch (action.type) {
    case 'PUT_MILK':
      return {...state, milk: state.milk + action.count};
    case 'TAKE_MILK':
      return {...state, milk: state.milk - action.count};
    default:
      return state;
  }
}

let store = createStore(reducer);

// subscribe其实就是订阅store的变化,一旦store发生了变化,传入的回调函数就会被调用
// 如果是结合页面更新,更新的操作就是在这里执行
store.subscribe(() => console.log(store.getState()));

// 将action发出去要用dispatch
store.dispatch({ type: 'PUT_MILK' });    // milk: 1
store.dispatch({ type: 'PUT_MILK' });    // milk: 2
store.dispatch({ type: 'TAKE_MILK' });   // milk: 1

手写 '/myredux'

const createStore = function(reducer){
    let state;
    const listeners = [];
    dispatch=function(action){
        state = reducer(state, action);
        listeners.map(item=>{
            item();
        })
    }
    subscribe=(fn)=>{
        listeners.push(fn);
    }
    getState=()=>return state;
    combineReducers(reducerMap) {
      //reducemap: { reducer }
      const reducerKeys = Object.keys(reducerMap);    // 先把参数里面所有的键值拿出来
  
      // 返回值是一个普通结构的reducer函数
      const reducer = (state = {}, action) => {
          const newState = {};
    
          for(let i = 0; i < reducerKeys.length; i++) {
              // reducerMap里面每个键的值都是一个reducer,我们把它拿出来运行下就可以得到对应键新的state值
              // 然后将所有reducer返回的state按照参数里面的key组装好
              // 最后再返回组装好的newState就行
              const key = reducerKeys[i]; //reducemap的key
              const currentReducer = reducerMap[key];
              const prevState = state[key]; //reducer的初始化state
              newState[key] = currentReducer(prevState, action);
        }
    
        return newState;
    };
  
    return reducer;
}
    const store = {
        dispatch,        
        subscribe,
        getstate
    }
    return store;
}

export default {
    createStore
}