实现拖拽
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 obj和delete 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
}