下面的promise有我敲写分析的过程视频
数组原型方法的实现
其实下面的这几个方法的写法差不多,参数也只有一些细微差别,但是要考虑到,如果是要返回一个新的数组的方法比如map,reduce都要用到深拷贝
forEach
这里主要需要注意的就是第一个参数是回调函数
,然后这个回调 函数也会有三个参数,分别是元素值,元素索引,遍历数组本身,第二个参数就是数组遍历时this的指向地址,下面的filter,map都是这样的参数类型
Array.prototype._forEach = function (cb, ...ret) {
if (typeof cb !== 'function') {
console.log('first param must be function');
}
if (!Array.isArray(this)) {
throw new Error('is not a array');
}
// console.log(cb);
// console.log(ret[0]);
let arr = this;
let len = arr.length;
let arg = ret[0] || window;
//fo循环每次都可以调用这个回调函数的一些逻辑处理
for (let i = 0; i < len; i++) {
cb.call(arg, arr[i], i, arr);
}
};
const arr = [1, 2, 3];
const obj = { a: 1 };
// arr.forEach(function (item, index, array) {
// console.log(this); //可以传第二个参数,如果第二个参数是obj,那么this就是
// // 指向的是obj
// console.log(item, index, array);
// }, obj);
arr._forEach(function (item, index, arr) {
console.log(item, index, arr);
}, obj);
map
这里我觉得唯独需要注意的就是,返回一个新的数组,需要用到深拷贝
Array.prototype._map = function (cb, ret) {
let arr = this;
let res = [];
if (!Array.isArray(this)) {
throw new Error('is not a array');
}
for (let i = 0; i < arr.length; i++) {
// 如果数据里面有对象等引用类型,那么就需要执行深拷贝
//res.push(cb.call(ret, deepClone(arr[i]), i, arr));
res.push(cb.call(ret, arr[i], i, arr));
}
return res;
};
const arr = [1, 2, 3];
const obj = { a: 1 };
let newArr = arr._map(function (item, index, arr) {
console.log(item, index, arr);
return item * 2;
}, obj);
console.log(newArr);
console.log(arr);
filter
逻辑上的处理差不多和上面的
Array.prototype._filter = function (cb) {
let arr = this;
let arg = arguments[1];
let res = [];
if (!Array.isArray(arr)) {
throw new Error('is not a array');
}
for (let i = 0; i < arr.length; i++) {
let flag = cb.call(arg, arr[i], i, arr);
if (flag) {
res.push(arr[i]);
}
}
return res;
};
const arr = [1, 2, 3];
let newArr = arr._filter(function (item) {
return item > 1;
});
console.log(newArr);
every
会写这个了肯定会写some方法了
Array.prototype._every = function (cb) {
let arr = this;
let arg = arguments[1] || window;
for (let i = 0; i < arr.length; i++) {
let flag = cb.call(arg, arr[i], i, arr);
if (!flag) {
return false;
}
}
return true;
};
const arr = [1, 2, 3];
let res = arr._every(function (item, index, array) {
return item > 0;
});
console.log(res);
reduce
这个也是需要深拷贝的
Array.prototype._reduce = function (cb, initialValue) {
let arr = this;
let item;
for (let i = 0; i < arr.length; i++) {
// item = deepClone(arr[i]); 这里是需要深拷贝的
initialValue = cb.call(arr, initialValue, arr[i], i, arr);
}
return initialValue;
};
const arr = [1, 3, 3];
let newArr = arr._reduce(function (pres, cur) {
return pres + cur;
}, 0);
console.log(newArr);
对象原型方法的实现
object.assign
对于Object.assign需要注意的一点就是,合并对象的属性时,后面合并的有相同属性会覆盖掉前面合并的相同属性,而且切记,它只能合并可枚举的属性,也就是enumable为true的属性,思路很简单,由于object.assgin是浅拷贝只用简单的复制语句就能复制指针,于是第一层遍历合并的一些对象,然后第二层遍历对象的各个属性然后进行合并
// 浅拷贝
Object._assign = function (target, ...source) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
// console.log(source);
let ret = Object(target);
source.forEach((obj) => {
if (obj != null) {
for (let key in obj) {
console.log(key);
// 可枚举的属性才会拷贝
if (obj.hasOwnProperty(key)) {
ret[key] = obj[key];
}
}
}
});
};
const a = {
x: 1,
y: {
z: 1,
},
};
const b = {
x: 2,
name: 'af',
hobbies: {
sports: 'basketball',
music: {
style: ['fuck', 'blues'],
instruments: ['guitar'],
},
},
};
// Object.assign(a, b);
Object._assign(a, b);
console.log(a);
附加一到关于Object.assign的题
// 面试题
const v1 = 123;
const v2 = '123';
const v3 = true;
const v4 = function test() {};
const v5 = Object.assign({}, v1, v2, v3, v4);
console.log(v5);
// 打印结果 :{0: "1", 1: "2", 2: "3"}
// 结果很巧妙:这是因为Object.assign会自动将不是对象的方法转化为对象
// 然后只会合并可枚举的属性,而上面拥有可枚举的属性的对象就只有String对象
const r1 = new Number(v1);
const r2 = new String(v2);
const r3 = new Boolean(v3);
const r4 = new Function(v4);
console.log(r1);
console.log(r2);
console.log(r3);
console.log(r4);
for (let key in r1) {
console.log(key);
}
for (let key in r2) {
console.log(key); //唯独只有这里可以打印
}
for (let key in r3) {
console.log(key);
}
for (let key in r4) {
console.log(key);
}
object.create
用这个方法能够非常简单的把组合继承变为寄生组合继承,由于它是利用了父构造函数的原型对象,创建一个新的原型对象来使子构造函数的__proto__ 来指向实现继承,相对于组合继承少了一次访问构造函数的有点
第二个参数是设置对象自己的属性,而且必须是一个对象descripter描述器,所以直接可以用Object.definepropert实现
// 第二个参数是设置对象自己的属性,而且必须是一个对象descripter描述器
Object._create = function (proto, propertyObject = undefined) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError(
'Object prototype may only be Object or null to Object',
);
}
if (propertyObject == null) {
new TypeError('cannot convert undefined or null to Object');
}
// 关键就这三行
function F() {}
F.prototype = proto;
const obj = new F();
if (propertyObject !== undefined) {
Object.defineProperty(obj, propertyObject);
}
if (proto === null) {
// 创建一个没有原型的对象 Object.create(null)
obj._proto_ = null;
}
return obj;
};
手写promise
這個相比不用多说有多重要了,天天用到的,所以在学习实现手写之前必须掌握好的它的一些特点和细节。
视频讲解地址
由于细节有点多,先写个只完成基本功能的,还有一些promise方法,没写我准备会在b站上发布一个视频来解析我的代码并贴在这里,如果一直没发布希望有人评论提醒我
下面代码有详细注释
class Af {
// 先定义三种状态
static PENGDING = 'pengding';
static FULLFILLED = 'fullfilled';
static REJECTED = 'rejected';
constructor(executor) {
this.status = Af.PENGDING;
this.value = '';
// 把状态改变后的函数放到下面数组中执行
// 等状态改变之后再拿出来执行
this.callbacks = [];
// 因为resolve和reject方法是在executor中调用,作用域也是executor的作用域
// 这样会造成this指向window,现在我么使用的是class定义,this会是undefined
// 所以下面绑定了this
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
// 当执行者出现异常时触发拒绝状态
this.reject(error);
}
}
resolve(value) {
// 状态只能改变一次所以在resolve和reject都需要添加状态条件判断
// 而且之能是 pending->fullfilled pending->rejected
if (this.status == Af.PENGDING) {
this.status = Af.FULLFILLED;
this.value = value;
// 添加处理callback方法的代码 异步处理s
setTimeout(() => {
this.callbacks.forEach((callback) => callback.onFullfilled(value));
});
}
}
reject(value) {
// 状态只能改变一次所以在resolve和reject都需要添加状态条件判断
// 而且之能是 pending->fullfilled pending->rejected
if (this.status == Af.PENGDING) {
this.status = Af.REJECTED;
this.value = value;
// 添加处理callback方法的代码 异步处理
setTimeout(() => {
this.callbacks.forEach((callback) => callback.onRejected(value));
});
}
}
// onFullfilled,onRjected即成功和错误时的回调函数
then(onFullfilled, onRejected) {
// then可以有两个参数,即成功和错误时的回调函数
// then的函数参数都不是必须的,所以需要设置默认为函数
// 用于处理当没有传递时的情况
if (typeof onFullfilled !== 'function') {
onFullfilled = (value) => value;
}
if (typeof onRejected !== 'function') {
onRejected = (value) => value;
}
// 返回一个新的promise对象 ,使.then能够链式调用
return new Af((resolve, reject) => {
// .then处理pengding状态(如果rejected被异步回调函数包裹,比如用setTimeOut包裹的
// 时候,状态是异步被改变的,那么没有下面Pending状态的处理,将不会执行.then方法,因为
// 定时函数之前一直没有执行resolve方法)
if (this.status == Af.PENGDING) {
// 将then方法的回调函数添加到 callbacks 数组中,用于异步执行
this.callbacks.push({
onFullfilled: (value) => {
try {
let res = onFullfilled(value);
resolve(res);
} catch (error) {
reject(error);
}
},
onRejected: (value) => {
try {
let res = onRejected(value);
reject(res);
} catch (error) {
reject(error);
}
},
});
}
// .then处理fullfilled状态
if (this.status == Af.FULLFILLED) {
// setTimeOut的作用是使.then回调函数其异步执行
setTimeout(() => {
try {
let res = onFullfilled(this.value);
resolve(res);
} catch (error) {
reject(error);
}
});
}
// .then处理rejected状态
if (this.status == Af.REJECTED) {
setTimeout(() => {
try {
let res = onRejected(this.value);
reject(res);
} catch (error) {
reject(error);
}
});
}
});
}
}
// debugger;
let p = new Af((resolve, reject) => {
// console.log(a);
setTimeout(() => {
resolve('阿锋');
// console.log(a);
});
})
.then((value) => {
console.log(value);
return 1;
})
.then((value) => {
console.log(value);
});
console.log('同步代码');
// console.log(p);
实现vue数据的双向绑定
我这里是用proxy实现的,其实vue2是用Object.defineproperty实现的。但是Proxy语法更简单,下面代码不仅仅实现,而且用到了class更具有面向对象的思想,可复用性高
看不懂可以下面评论一起讨论,有机会我也想出个视频解说一些,教学相长
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<div>
<input type="text" id="username" v-model="username">
<input type="text" id="sex" v-model="sex">
<input type="text" id="age" v-model="age">
</div>
<div>
<p>姓名:<span>{{username}}</span></p>
<p>性别:<span>{{sex}}</span></p>
<p>年龄:<span>{{age}}</span></p>
</div>
</div>
<script>
class MVVM{
constructor(el,data){
this.el = document.querySelector(el)
this.data = data
this.domPool = {}
this.init()
}
init(){
this.initDom(this.el)
this.watchData()
this.bindInput(this.el)
}
initDom(el){
const childNodes = el.childNodes
// console.log(childNodes);
childNodes.forEach(item=>{
// console.log(item);
if(item.nodeType === 3){
const text = item.nodeValue
// console.log(text);
let reg = /\{\{(.+?)\}\}/
// /\{\{(.+?)\}\}/
if(text.trim().length){
let isValid = reg.test(text)
if(isValid){
let key = text.match(reg)[1].trim()
// console.log(key);
this.domPool[key] = item.parentNode
// 使得dom节点能够显示传入进来的this.data数据
item.parentNode.innerHTML = this.data[key] || undefined
}
}
}
item.childNodes&&this.initDom(item)
})
}
watchData(){
let _this = this
this.data = new Proxy(this.data,{
get(target,key){
console.log(`获取数据: ${key}`);
return Reflect.get(target,key)
},
set(target,key,value){
console.log(`设置数据: ${key}`);
// 设置数据时 顺便改变当前监听节点的text值
_this.domPool[key].innerHTML = value
return Reflect.set(target,key,value)
}
})
}
bindInput(el){
let allInputs = document.querySelectorAll('input')
// console.log(allInputs);
allInputs.forEach(input=>{
let key = input.getAttribute('v-model')
input.addEventListener('keyup',()=>{
// 设置对应的key为input的值 并监听keyup事件 动态绑定成功
this.data[key] = input.value
},false)
})
}
}
let vm = new MVVM('#app',{
username:'阿锋',
sex:'man',
age:'22'
})
</script>
</body>
</html>
到这里 这次的总结就算完了 如有错误 恳请指针