阅读源码,学习前人的经验以提升自己的能力。源码往往是前人留下的最佳实践,我们跟着前人的脚步去学习更会让我们事半功倍。
原理:
- 获取构造函数与参数;
- 继承foo.prototype的属性;
- 继承构造函数中的属性;
- 返回新对象。
实现:
let myNew = function(){
let foo = arguments[0],
params = Array.prototype.slice.call(arguments, 1);
let obj = Object.create(foo.prototype);
let nObj = foo.apply(obj, params);
return typeof nObj === 'object' ? nObj : obj;
}
测试:
function P(name, words){
this.name = name;
this.say = function(){
console.log(words);
}
}
let p = myNew(P, 'Jeff', 'Hi!');
原理: call、apply、bind本质都是改变this的指向。不同点在于call以逗号分隔方式传参、apply以数组形式传参,此外call、apply都是绑定函数并执行返回结果,而bind是绑定函数但不执行。bind方法即可在bind中传参也可在执行时传参,实例化时this指向会失效(即,new一个对象时指向实例化对象,否则指向被绑定的context)。
bind实现:
- 预热方法
Function.prototype.myBind = function(context){
let _self = this,
args = Array.prototype.slice.call(arguments, 1);
let fn = function(){
let newArgs = Array.prototype.slice.call(arguments),
newThis = this instanceof _self ? this : context;
//1,修正this指向错误问题。
_self.apply(newThis, args.concat(newArgs);)
}
fn.prototype = _self.prototype;
//2,通过原型链使实例化对象可以继承绑定函数中的属性。
return fn;
}
var person = Person.myBind(p, 'Jeff'); var obj = new person('male'); 此时在fn = function(){...}中console.log(this, _self)可以发现,this指向实例化对象obj(有2语句时,obj中会有name与gender属性;没有该语句时,obj会被实例化成一个空对象),_self指向绑定函数Person(){...}。
var person = Person.myBind(p, 'Jeff')(); 此时在fn = function(){...}中console.log(this, _self)可以发现,this指向window(出现了this指向错误的问题),_self指向绑定函数Person(){...},因此在程序中需要通过newThis与apply对this的指向进行修正。
- 圣杯模式
Function.prototype.myBind = function(context){
let _self = this,
args = Array.prototype.slice.call(arguments, 1),
Buffer = function(){};
let fn = function(){
let newArgs = Array.prototype.slice.call(arguments),
newThis = this instanceof _self ? this : context;
_self.apply(newThis, args.concat(newArgs));
}
Buffer.prototype = _self.prototype;
fn.prototype = new Buffer();
return fn;
}
call实现:
Function.prototype.myCall = function(context){
if(typeof this !== 'function){
throw new TypeError(`${this} is not a function`); // 调用call的若不是函数则报错
}
let args = [...arguments].slice(1);
context = context || window;
context[fn] = this; // 将调用call函数的对象添加到context的属性中
let res = context[fn](...args); // 执行该属性
delete context[fn]; // 删除该属性
return res;
}
apply实现:
Function.prototype.myApply = function(context){
if(typeof this !== 'function){
throw new TypeError(`${this} is not a function`);
}
let args = arguments[1];
context = context || window;
context[fn] = this;
let res = context[fn](...args);
delete context[fn];
return res;
}
原理:
浅拷贝无法拷贝对象、数组这样的引用值属性,目标对象修改引用值属性会导致原对象的也被修改。深拷贝可以拷贝对象、数组这样的引用值属性。目标对象修改引用值属性不影响原对象。
实现:
- 浅拷贝
function clone(target, origin){
let target = target || {};
for(let key in origin){
// 原型上的属性会被拷贝到target中,因此需要剔除。
if(origin.hasOwnProperty(key)){
target[key] = origin[key];
}
}
return target;
}
- 深拷贝
function deepClone(target, origin){
let target = target || {},
toStr = Object.prototype.toString(),
arrType = '[object Array]';
for(let key in origin){
if(typeof origin[key] === 'object' && origin[key] !== null){
toStr.call(origin[key]) === arrType ? target[key] = [] : target[key] = {};
deepClone(target[key], origin[key]);
}else{
target[key] = origin[key];
}
}
}
测试:
// 浅拷贝
var person1 = clone(person); // 或
var person1 = {}; clone(person, person1);
person1.name = 'Tom';
// 此时不会影响person的该属性。
person1.children.third={name: 'Ben', age: 8};
// 此时person的children属性也被修改。
// 深拷贝
var person2 = deepClone(person); // 或
var person2 = {}; deepClone(person, person2);
person2.name = 'Tencent';
//此时不会影响person的该属性。
person2.children.fourth = {name: 'Mary', age: 1};
//此时person的children属性不会被修改。
原理:
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
重写:
if (typeof Object.create !== "function") {
Object.create = function (prototype, properties) {
if (typeof prototype !== "object") { throw TypeError(); }
function Ctor() {}
Ctor.prototype = prototype;
let o = new Ctor();
if (prototype) { o.constructor = Ctor; }
if (properties !== undefined) {
if (properties !== Object(properties)) { throw TypeError(); }
Object.defineProperties(o, properties);
}
return o;
};
}
原理:
- 创建一个中间类,使子类原型指向该中间类的实例化空对象,目的是子类在修改属性时不会影响父类的原型;
- 修改构造函数指向;
- 添加超类,指明或标记继承于谁(即Target生父)。
实现:
- 圣杯模式
function inherit(Target, Origin){
function Buffer(){};
Buffer.prototype = Origin.prototype;
Target.prototype = new Buffer();
Target.prototype.constructor = Target;
Target.prototype.uber = Origin;
}
- 圣杯模式(雅虎版)
利用闭包将Buffer变成私有化构造函数。其在函数执行完后会被销毁,但是他传递原型的功能已经实现。
let inherit = (function(){
let Buffer = function(){};
return function(Target, Origin){
Buffer.prototype = Origin.prototype;
Target.prototype = new Buffer();
Target.prototype.constructor = Target;
Target.prototype.uber = Origin;
}
})();
- 我的实现思路
Object.create()创建了一个空对象,空对象的原型指向Origin.prototype, Target的原型指向该空对象。与圣杯模式中创建buffer有异曲同工之妙。
let inherit = function(Target, Origin){
Target.prototype = Object.create(Origin.prototype);
Target.prototype.constructor = Target;
Target.prototype.uber = Origin;
}
- ES6中继承的实现
class Father {
constructor(name,age){
this.name = name;
this.age = age;
}
show(){
console.log(`我的名字是${this.name},今年${this.age}岁。`);
}
}
class Son extends Father{};
let s = new Son('Jeff',24);
原理:
L 的 proto 是不是等于 R.prototype,不等于再找 L.proto.proto 直到 proto 为 null (L 表示左表达式,R 表示右表达式)
实现:
function myInstanceof(L, R) {
let O = R.prototype;
L = L.__proto__;
while (true) {
if (L === null) return false;
if (O === L) return true; // 重点:当 O 严格等于 L 时,返回 true
L = L.__proto__;
}
}
Array.myIsArray = function(o) {
return Object.prototype.toString.call(Object(o)) === '[object Array]';
};
原理:
- 构造函数接收一个 executor 函数,并会在 new Promise() 时立即执行该函数
- then 时收集依赖,将回调函数收集到 成功/失败队列
- executor 函数中调用 resolve/reject 函数
- resolve/reject 函数被调用时会通知触发队列中的回调
const isFunction = variable => typeof variable === 'function';
// 定义Promise的三种状态常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
// 构造函数,new时触发
constructor(handle: Function) {
try {
handle(this._resolve, this._reject);
} catch (err) {
this._reject(err);
}
}
// 状态 pending fulfilled rejected
private _status: string = PENDING;
// 储存 value,用于 then 返回
private _value: string | undefined = undefined;
// 失败队列,在 then 时注入,resolve 时触发
private _rejectedQueues: any = [];
// 成功队列,在 then 时注入,resolve 时触发
private _fulfilledQueues: any = [];
// resovle 时执行的函数
private _resolve = val => {
const run = () => {
if (this._status !== PENDING) return;
this._status = FULFILLED;
// 依次执行成功队列中的函数,并清空队列
const runFulfilled = value => {
let cb;
while ((cb = this._fulfilledQueues.shift())) {
cb(value);
}
};
// 依次执行失败队列中的函数,并清空队列
const runRejected = error => {
let cb;
while ((cb = this._rejectedQueues.shift())) {
cb(error);
}
};
/*
* 如果resolve的参数为Promise对象,
* 则必须等待该Promise对象状态改变后当前Promsie的状态才会改变
* 且状态取决于参数Promsie对象的状态
*/
if (val instanceof MyPromise) {
val.then(
value => {
this._value = value;
runFulfilled(value);
},
err => {
this._value = err;
runRejected(err);
}
);
} else {
this._value = val;
runFulfilled(val);
}
};
// 异步调用
setTimeout(run);
};
// reject 时执行的函数
private _reject = err => {
if (this._status !== PENDING) return;
// 依次执行失败队列中的函数,并清空队列
const run = () => {
this._status = REJECTED;
this._value = err;
let cb;
while ((cb = this._rejectedQueues.shift())) {
cb(err);
}
};
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run);
};
// then 方法
then(onFulfilled?, onRejected?) {
const { _value, _status } = this;
// 返回一个新的Promise对象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
// 封装一个成功时执行的函数
const fulfilled = value => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value);
} else {
const res = onFulfilled(value);
if (res instanceof MyPromise) {
// 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
res.then(onFulfilledNext, onRejectedNext);
} else {
//否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
onFulfilledNext(res);
}
}
} catch (err) {
// 如果函数执行出错,新的Promise对象的状态为失败
onRejectedNext(err);
}
};
// 封装一个失败时执行的函数
const rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error);
} else {
const res = onRejected(error);
if (res instanceof MyPromise) {
// 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
res.then(onFulfilledNext, onRejectedNext);
} else {
//否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
onFulfilledNext(res);
}
}
} catch (err) {
// 如果函数执行出错,新的Promise对象的状态为失败
onRejectedNext(err);
}
};
switch (_status) {
// 当状态为pending时,将then方法回调函数加入执行队列等待执行
case PENDING:
this._fulfilledQueues.push(fulfilled);
this._rejectedQueues.push(rejected);
break;
// 当状态已经改变时,立即执行对应的回调函数
case FULFILLED:
fulfilled(_value);
break;
case REJECTED:
rejected(_value);
break;
}
});
}
// catch 方法
catch(onRejected) {
return this.then(undefined, onRejected);
}
// finally 方法
finally(cb) {
return this.then(
value => MyPromise.resolve(cb()).then(() => value),
reason =>MyPromise.resolve(cb()).then(() => {
throw reason;
})
);
}
// 静态 resolve 方法
static resolve(value) {
// 如果参数是MyPromise实例,直接返回这个实例
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
// 静态 reject 方法
static reject(value) {
return new MyPromise((resolve, reject) => reject(value));
}
// 静态 all 方法
static all(list) {
return new MyPromise((resolve, reject) => {
// 返回值的集合
let values = [];
let count = 0;
for (let [i, p] of list.entries()) {
// 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
this.resolve(p).then(
res => {
values[i] = res;
count++;
// 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
if (count === list.length) resolve(values);
},
err => {
// 有一个被rejected时返回的MyPromise状态就变成rejected
reject(err);
}
);
}
});
}
// 添加静态race方法
static race(list) {
return new MyPromise((resolve, reject) => {
for (let p of list) {
// 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
this.resolve(p).then(
res => {
resolve(res);
},
err => {
reject(err);
}
);
}
});
}
}
防抖函数(debounce):onscroll 结束时触发一次,延迟执行。
// fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
function debounce(fn, delay) {
// 定时器
let timer = null;
// 将debounce处理结果当作函数返回
return function () {
// 保留调用时的this上下文
let context = this;
// 保留调用时传入的参数
let args = arguments;
// 每次事件被触发时,都去清除之前的旧定时器
if(timer) {
clearTimeout(timer);
}
// 设立新定时器
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
}
}
// 用debounce来包装scroll的回调
const better_scroll = debounce(() => console.log('触发了滚动事件'), 1000);
document.addEventListener('scroll', better_scroll);
节流函数(throttle):onscroll 时,每隔一段时间触发一次,像水滴一样。
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
// last为上一次触发回调的时间
let last = 0;
// 将throttle处理结果当作函数返回
return function () {
// 保留调用时的this上下文
let context = this;
// 保留调用时传入的参数
let args = arguments;
// 记录本次触发回调的时间
let now = +new Date();
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (now - last >= interval) {
// 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
last = now;
fn.apply(context, args);
}
}
}
// 用throttle来包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000);
document.addEventListener('scroll', better_scroll)
用 Throttle 来优化 Debounce
// fn是我们需要包装的事件回调, delay是时间间隔的阈值
function throttle(fn, delay){
// last为上一次触发回调的时间, timer是定时器
let last = 0, timer = null;
// 将throttle处理结果当作函数返回
return function () {
// 保留调用时的this上下文
let context = this;
// 保留调用时传入的参数
let args = arguments;
// 记录本次触发回调的时间
let now = +new Date();
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if(now - last < delay){
// 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
clearTimeout(timer);
timer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, delay);
}else{
// 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
last = now;
fn.apply(context, args);
}
}
}
// 用新的throttle包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)
defineProperty版本:
<section>
输入文本:<input type="text" id="input">
展示文本:<span id="span"></span>
</section>
<script>
let input = document.getElementById('input');
let span = document.getElementById('span');
let data = {};
// 数据劫持
Object.defineProperty(data,"text",{
//设置text属性时会自动触发set方法,数据变化 --> 修改视图
set: (newVal)=>{
input.value = newVal;
span.innerHTML = newVal;
}
});
// 视图更改 --> 数据变化
input.addEventListener('keyup',(event)=>{
data.text = event.target.value;
});
</script>
proxy版本:
let input = document.getElementById('input');
let span = document.getElementById('span');
let data = {};
// 数据劫持
let handler = {
set(target, key, value) {
target[key] = value;
// 数据变化 --> 修改视图
input.value = value;
span.innerHTML = value;
}
};
let proxy = new Proxy(data, handler);
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
proxy.text = e.target.value;
});
function myTypeof(val){
let type = typeof(val),
toStr = Object.prototype.toString;
var res = {
'[object Object]': 'Object',
'[object Array]': 'Array',
'[object Number]': 'object Number',
'[object String]': 'object String',
'[object Boolean]': 'object Boolean',
'[object Date]': 'Date',
'[object RegExp]': 'RegExp'
}
if(val === null){
return null;
}else if(type === 'object'){
let ret = toStr.call(val);
return res[ret];
}else{
return type;
}
}
function addEvent(elem, type, handle){
if(elem.addEventListener){
elem.addEventListener(type, handle, false);
}else if(elem.attachEvent){ // 兼容IE
elem.attachEvent('on'+type, function(){
handle.call(elem);
});
}else{
elem['on' + type] = handle;
}
}
<!--输出li中内容-->
<div>
<ul id="ull">
<li>
<a href="javascript:;">anchor label</a>
</li>
<li>
text
</li>
<li>
<span>span1</span>
<span>span2</span>
</li>
</ul>
</div>
<script>
;(function(){
var oUl = document.getElementById("ull");
oUl.addEventListener('click', function(eve){
handler(eve)
}, false);
function handler(eve){
var eve = eve || window.event;
var target = eve.target || eve.srcElement;
while(target != oUl){
if(target.nodeName.toLowerCase() === 'li'){
console.log(target.innerText);
}
target = target.parentNode;
//与while循环一起实现,当点击li子标签依然能触发事件
}
}
})();
</script>
let opt = {
url: 'test.txt',
type: 'get',
data: {
name:'Jeff',
age:24
}
};
function ajax(opt){
if(opt.url && opt.type){
let xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
let url = opt.url,
type = opt.type.toUpperCase(),
data = opt.data,
dataArr = [];
for(let key in data){
dataArr.push(key + '=' +data[key]);
}
if(type == 'GET'){
url = url + '?' + dataArr.join('&');
xhr.open(type, url.replace(/\?$/g,''), true);//true表示异步
xhr.send();
}
if(type == 'POST'){
xhr.open(type, url, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send();
}
//方法1
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
var res = xhr.responseText;
console.log(res);
}else{
console.log("Request was unsuccessful: " + xhr.readyState);
}
}
//方法2
xhr.onload = function(){
if((xhr.status>=200 && xhr.status<300) || xhr.status==304){
let res = xhr.responseText;
console.log(res);
}else{
console.log("Request was unsuccessful: " + xhr.status);
}
}
}else{
console.log("URL or Send Type Error !");
}
}
let opt = {
url: 'test.txt',
type: 'get',
data: {
name:'Jeff',
age:24
}
};
function ajax(opt){
let url = opt.url,
type = opt.type.toUpperCase(),
data = opt.data,
dataArr = [];
for(let key in data){
dataArr.push(key + '=' +data[key]);
}
return new Promise(){
let xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
if(type == 'GET'){
url = url + '?' + dataArr.join('&');
xhr.open(type, url.replace(/\?$/g,''), true);//true表示异步
xhr.send();
}
if(type == 'POST'){
xhr.open(type, url, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send();
}
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
let data = xhr.responseText;
resolve(data);
}else{
let errMsg = {
'code': xhr.status,
'msg': xhr.response
}
reject(errMsg);
}
}
}
}
ajax.then(function(data){
console.log(data);
}).catch(function(err) {
console.error(err);
})