js高级用法----手写js原生方法
1、call 方法
/**
* _call
*
* @param { context } context
* @param { arguments } arguments
*/
Function.prototype._call = function(context) {
// 如果没有传或传的值为空对象 context指向window
context = context || window
let fn = Symbol(context)
context[fn] = this //给context添加一个方法 指向this
// 处理参数 去除第一个参数this 其它传入fn函数
let args = [...arguments].slice(1) //[...xxx]把类数组变成数组,arguments为啥不是数组自行搜索 slice返回一个新数组
context[fn](...args) //执行fn
delete context[fn] //删除方法
}
var obj = {
name: 'Bob',
age: '18',
fn() {
console.log(`My name is ${this.name}, my age is ${this.age}`)
}
}
var dog = {
name: 'Snoopy',
age: 3
}
obj.fn.call(dog,'daddata','ttt','yuyuyuuy') // My name is Snoby, my age is 3
obj.fn._call(dog,'daddata','ttt','yuyuyuuy') // My name is Snoby, my age is 3
2、 apply 方法
Function.prototype._apply = function(context) {
// 如果没有传或传的值为空对象 context指向window
context = context || window
let fn = Symbol(context)
context[fn] = this
let arg = [...arguments].slice(1)
context[fn](arg) //执行fn
delete context[fn] //删除方法
}
3、bind方法
/**
* _bind
*
* @param {*} context
*/
Function.prototype._bind = function (context) {
//返回一个绑定this的函数,我们需要在此保存this
let self = this
// 可以支持柯里化传参,保存参数
let arg = [...arguments].slice(1)
// 返回一个函数
return function () {
//同样因为支持柯里化形式传参我们需要再次获取存储参数
let newArg = [...arguments]
// 返回函数绑定this,传入两次保存的参数
//考虑返回函数有返回值做了return
return self.apply(context, arg.concat(newArg))
}
}
4、promise方法
function MyPromise(exceptor) {
let _this = this;
_this.reason = '';
_this.value = '';
_this.resolveFnArr = [];
_this.rejectFnArr = [];
_this.status = 'pending';
function resolve(val) {
if (_this.status === 'pending') {
_this.value = val;
_this.status = 'fulfiled';
_this.resolveFnArr.forEach(fn => fn())
}
}
function reject(reason) {
if (_this.status === 'pending') {
_this.reason = reason;
_this.status = 'reject';
_this.rejectFnArr.forEach(fn => fn())
}
}
try {
exceptor(resolve, reject);
} catch (error) {
reject();
}
}
MyPromise.prototype.then = function(resolve, reject) {
let _this = this;
if (_this.status === 'fulfiled') {
resolve(_this.value);
}
if (_this.status === 'reject') {
reject(_this.reason);
}
if (_this.status === 'pending') {
_this.resolveFnArr.push(() => {resolve(_this.value)})
_this.rejectFnArr.push(() => {reject(_this.reason)})
}
}
5、全面的promise写法
(function(window,undefined){
// resolve 和 reject 最终都会调用该函数
var final = function(status,value){
var _this = this, fn, status;
if(_this._status !== 'PENDING') return;
// 所以的执行都是异步调用,保证then是先执行的
setTimeout(function(){
_this._status = status;
status = _this._status === 'FULFILLED'
queue = _this[status ? '_resolves' : '_rejects'];
while(fn = queue.shift()) {
value = fn.call(_this, value) || value;
}
_this[status ? '_value' : '_reason'] = value;
_this['_resolves'] = _this['_rejects'] = undefined;
});
}
//参数是一个函数,内部提供两个函数作为该函数的参数,分别是resolve 和 reject
var MyPromise = function(resolver){
if (!(typeof resolver === 'function' ))
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
//如果不是promise实例,就new一个
if(!(this instanceof MyPromise)) return new MyPromise(resolver);
var _this = this;
_this._value;
_this._reason;
_this._status = 'PENDING';
//存储状态
_this._resolves = [];
_this._rejects = [];
//
var resolve = function(value) {
//由於apply參數是數組
final.apply(_this,['FULFILLED'].concat([value]));
}
var reject = function(reason){
final.apply(_this,['REJECTED'].concat([reason]));
}
resolver(resolve,reject);
}
MyPromise.prototype.then = function(onFulfilled,onRejected){
var _this = this;
// 每次返回一个promise,保证是可thenable的
return new MyPromise(function(resolve,reject){
function handle(value) {
// 這一步很關鍵,只有這樣才可以將值傳遞給下一個resolve
var ret = typeof onFulfilled === 'function' && onFulfilled(value) || value;
//判断是不是promise 对象
if (ret && typeof ret ['then'] == 'function') {
ret.then(function(value) {
resolve(value);
}, function(reason) {
reject(reason);
});
} else {
resolve(ret);
}
}
function errback(reason){
reason = typeof onRejected === 'function' && onRejected(reason) || reason;
reject(reason);
}
if(_this._status === 'PENDING'){
_this._resolves.push(handle);
_this._rejects.push(errback);
}else if(_this._status === FULFILLED){ // 状态改变后的then操作,立刻执行
callback(_this._value);
}else if(_this._status === REJECTED){
errback(_this._reason);
}
});
}
MyPromise.prototype.catch = function(onRejected){
return this.then(undefined, onRejected)
}
MyPromise.prototype.delay = function(ms,value){
return this.then(function(ori){
return MyPromise.delay(ms,value || ori);
})
}
MyPromise.delay = function(ms,value){
return new MyPromise(function(resolve,reject){
setTimeout(function(){
resolve(value);
console.log('1');
},ms);
})
}
MyPromise.resolve = function(arg){
return new MyPromise(function(resolve,reject){
resolve(arg)
})
}
MyPromise.reject = function(arg){
return MyPromise(function(resolve,reject){
reject(arg)
})
}
MyPromise.all = function(promises){
if (!Array.isArray(promises)) {
throw new TypeError('You must pass an array to all.');
}
return MyPromise(function(resolve,reject){
var i = 0,
result = [],
len = promises.length,
count = len
//这里与race中的函数相比,多了一层嵌套,要传入index
function resolver(index) {
return function(value) {
resolveAll(index, value);
};
}
function rejecter(reason){
reject(reason);
}
function resolveAll(index,value){
result[index] = value;
if( --count == 0){
resolve(result)
}
}
for (; i < len; i++) {
promises[i].then(resolver(i),rejecter);
}
});
}
MyPromise.race = function(promises){
if (!Array.isArray(promises)) {
throw new TypeError('You must pass an array to race.');
}
return MyPromise(function(resolve,reject){
var i = 0,
len = promises.length;
function resolver(value) {
resolve(value);
}
function rejecter(reason){
reject(reason);
}
for (; i < len; i++) {
promises[i].then(resolver,rejecter);
}
});
}
window.MyPromise = MyPromise;
})(window);
6、filter
Array.prototype.filter = function(callback, thisArg) {
if (this == undefined) {
throw new TypeError('this is null or not undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + 'is not a function');
}
const res = [];
// 让O成为回调函数的对象传递(强制转换对象)
const O = Object(this);
// >>>0 保证len为number,且为正整数
const len = O.length >>> 0;
for (let i = 0; i < len; i++) {
// 检查i是否在O的属性(会检查原型链)
if (i in O) {
// 回调函数调用传参
if (callback.call(thisArg, O[i], i, O)) {
res.push(O[i]);
}
}
}
return res;
}
7、map方法
Array.prototype.map = function(callback, thisArg) {
if (this == undefined) {
throw new TypeError('this is null or not defined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const res = [];
// 同理
const O = Object(this);
const len = O.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in O) {
// 调用回调函数并传入新数组
res[i] = callback.call(thisArg, O[i], i, this);
}
}
return res;
}
8、forEach方法
Array.prototype.forEach = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined');
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
const O = Object(this);
const len = O.length >>> 0;
let k = 0;
while (k < len) {
if (k in O) {
callback.call(thisArg, O[k], k, O);
}
k++;
}
}
9、reduce方法
Array.prototype.reduce = function(callback, initialValue) {
if (this == undefined) {
throw new TypeError('this is null or not defined');
}
if (typeof callback !== 'function') {
throw new TypeError(callbackfn + ' is not a function');
}
const O = Object(this);
const len = this.length >>> 0;
let accumulator = initialValue;
let k = 0;
// 如果第二个参数为undefined的情况下
// 则数组的第一个有效值作为累加器的初始值
if (accumulator === undefined) {
while (k < len && !(k in O)) {
k++;
}
// 如果超出数组界限还没有找到累加器的初始值,则TypeError
if (k >= len) {
throw new TypeError('Reduce of empty array with no initial value');
}
accumulator = O[k++];
}
while (k < len) {
if (k in O) {
accumulator = callback.call(undefined, accumulator, O[k], k, O);
}
k++;
}
return accumulator;
}
10、debounce(防抖)
const debounce = (fn, time) => {
let timeout = null;
return function() {
clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, time);
}
};
11、throttle(节流)
var throttle = function(func, delay) {
var prev = Date.now();
return function() {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
12、模拟new操作
function newOperator(ctor, ...args) {
if (typeof ctor !== 'function') {
throw new TypeError('Type Error');
}
const obj = Object.create(ctor.prototype);
const res = ctor.apply(obj, args);
const isObject = typeof res === 'object' && res !== null;
const isFunction = typeof res === 'function';
return isObject || isFunction ? res : obj;
}
13、instanceof
const myInstanceof = (left, right) => {
// 基本数据类型都返回false
if (typeof left !== 'object' || left === null) return false;
let proto = Object.getPrototypeOf(left);
while (true) {
if (proto === null) return false;
if (proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
14、原型继承
function Parent() {
this.name = 'parent';
}
function Child() {
Parent.call(this);
this.type = 'children';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
15、Object.assign
Object.defineProperty(Object, 'assign', {
value: function(target, ...args) {
if (target == null) {
return new TypeError('Cannot convert undefined or null to object');
}
// 目标对象需要统一是引用数据类型,若不是会自动转换
const to = Object(target);
for (let i = 0; i < args.length; i++) {
// 每一个源对象
const nextSource = args[i];
if (nextSource !== null) {
// 使用for...in和hasOwnProperty双重判断,确保只拿到本身的属性、方法(不包含继承的)
for (const nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
// 不可枚举
enumerable: false,
writable: true,
configurable: true,
})
16、深拷贝
const cloneDeep1 = (target, hash = new WeakMap()) => {
// 对于传入参数处理
if (typeof target !== 'object' || target === null) {
return target;
}
// 哈希表中存在直接返回
if (hash.has(target)) return hash.get(target);
const cloneTarget = Array.isArray(target) ? [] : {};
hash.set(target, cloneTarget);
// 针对Symbol属性
const symKeys = Object.getOwnPropertySymbols(target);
if (symKeys.length) {
symKeys.forEach(symKey => {
if (typeof target[symKey] === 'object' && target[symKey] !== null) {
cloneTarget[symKey] = cloneDeep1(target[symKey]);
} else {
cloneTarget[symKey] = target[symKey];
}
})
}
for (const i in target) {
if (Object.prototype.hasOwnProperty.call(target, i)) {
cloneTarget[i] =
typeof target[i] === 'object' && target[i] !== null
? cloneDeep1(target[i], hash)
: target[i];
}
}
return cloneTarget;
}
17、JSONP
const jsonp = ({ url, params, callbackName }) => {
const generateUrl = () => {
let dataSrc = '';
for (let key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
dataSrc += `${key}=${params[key]}&`;
}
}
dataSrc += `callback=${callbackName}`;
return `${url}?${dataSrc}`;
}
return new Promise((resolve, reject) => {
const scriptEle = document.createElement('script');
scriptEle.src = generateUrl();
document.body.appendChild(scriptEle);
window[callbackName] = data => {
resolve(data);
document.removeChild(scriptEle);
}
})
}
18、AJAX
const getJSON = function(url) {
return new Promise((resolve, reject) => {
const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
}
xhr.send();
})
}
19、图片懒加载
// 可以给img标签统一自定义属性data-src='default.png',当检测到图片出/现在窗口之后再补充src属性,此时才会进行图片资源加载。
function lazyload() {
const imgs = document.getElementsByTagName('img');
const len = imgs.length;
// 视口的高度
const viewHeight = document.documentElement.clientHeight;
// 滚动条高度
const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;
for (let i = 0; i < len; i++) {
const offsetHeight = imgs[i].offsetTop;
if (offsetHeight < viewHeight + scrollHeight) {
const src = imgs[i].dataset.src;
imgs[i].src = src;
}
}
}
20、滚动加载
// 原理就是监听页面滚动事件,分析clientHeight、scrollTop、scrollHeight三者的属性关系。
window.addEventListener('scroll', function() {
const clientHeight = document.documentElement.clientHeight;
const scrollTop = document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight;
if (clientHeight + scrollTop >= scrollHeight) {
// 检测到滚动至页面底部,进行后续操作
// ...
}
}, false);
21、渲染几万条数据不卡住页面
// 渲染大数据时,合理使用createDocumentFragment和requestAnimationFrame,将操作切分为一小段一小段执行。
setTimeout(() => {
// 插入十万条数据
const total = 100000;
// 一次插入的数据
const once = 20;
// 插入数据需要的次数
const loopCount = Math.ceil(total / once);
let countOfRender = 0;
const ul = document.querySelector('ul');
// 添加数据的方法
function add() {
const fragment = document.createDocumentFragment();
for(let i = 0; i < once; i++) {
const li = document.createElement('li');
li.innerText = Math.floor(Math.random() * total);
fragment.appendChild(li);
}
ul.appendChild(fragment);
countOfRender += 1;
loop();
}
function loop() {
if(countOfRender < loopCount) {
window.requestAnimationFrame(add);
}
}
loop();
}, 0)
22、将VirtualDom转化为真实DOM结构
// vnode结构:
// {
// tag,
// attrs,
// children,
// }
//Virtual DOM => DOM
function render(vnode, container) {
container.appendChild(_render(vnode));
}
function _render(vnode) {
// 如果是数字类型转化为字符串
if (typeof vnode === 'number') {
vnode = String(vnode);
}
// 字符串类型直接就是文本节点
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}
// 普通DOM
const dom = document.createElement(vnode.tag);
if (vnode.attrs) {
// 遍历属性
Object.keys(vnode.attrs).forEach(key => {
const value = vnode.attrs[key];
dom.setAttribute(key, value);
})
}
// 子数组进行递归操作
vnode.children.forEach(child => render(child, dom));
return dom;
}