实现 call 办法
call 做了什么:
- 将函数设为对象的属性
- 执行和删除这个函数
- 指定
this
到函数并传入给定参数执行函数 - 如果不传入参数,默认指向为
window
// 模仿 call bar.mycall(null);
// 实现一个 call 办法:// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()
Function.prototype.myCall = function(context = window, ...args) {if (typeof this !== "function") {throw new Error('type error')
}
// this-->func context--> obj args--> 传递过去的参数
// 在 context 上加一个惟一值不影响 context 上的属性
let key = Symbol('key')
context[key] = this; // context 为调用的上下文,this 此处为函数,将这个函数作为 context 的办法
// let args = [...arguments].slice(1) // 第一个参数为 obj 所以删除, 伪数组转为数组
// 绑定参数 并执行函数
let result = context[key](...args);
// 革除定义的 this 不删除会导致 context 属性越来越多
delete context[key];
// 返回后果
return result;
};
// 用法:f.call(obj,arg1)
function f(a,b){console.log(a+b)
console.log(this.name)
}
let obj={name:1}
f.myCall(obj,1,2) // 否则 this 指向 window
实现双向数据绑定
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {console.log('获取数据了')
},
set(newVal) {console.log('数据更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 输出监听
input.addEventListener('keyup', function(e) {obj.text = e.target.value})
实现数组的 flat 办法
function _flat(arr, depth) {if(!Array.isArray(arr) || depth <= 0) {return arr;}
return arr.reduce((prev, cur) => {if (Array.isArray(cur)) {return prev.concat(_flat(cur, depth - 1))
} else {return prev.concat(cur);
}
}, []);
}
手写 Object.create
思路:将传入的对象作为原型
function create(obj) {function F() {}
F.prototype = obj
return new F()}
模仿 Object.create
Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的__proto__。
// 模仿 Object.create
function create(proto) {function F() {}
F.prototype = proto;
return new F();}
替换 a,b 的值,不能用长期变量
奇妙的利用两个数的和、差:
a = a + b
b = a - b
a = a - b
参考 前端进阶面试题具体解答
实现数组的 map 办法
Array.prototype._map = function(fn) {if (typeof fn !== "function") {throw Error('参数必须是一个函数');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {res.push(fn(this[i]));
}
return res;
}
实现 instanceOf
// 模仿 instanceof
function instance_of(L, R) {
//L 示意左表达式,R 示意右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {if (L === null) return false;
if (O === L)
// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
实现数组去重
给定某无序数组,要求去除数组中的反复数字并且返回新的无反复数组。
ES6 办法(应用数据结构汇合):
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
ES5 办法:应用 map 存储不反复的数字
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
uniqueArray(array); // [1, 2, 3, 5, 9, 8]
function uniqueArray(array) {let map = {};
let res = [];
for(var i = 0; i < array.length; i++) {if(!map.hasOwnProperty([array[i]])) {map[array[i]] = 1;
res.push(array[i]);
}
}
return res;
}
手写节流函数
函数节流是指规定一个单位工夫,在这个单位工夫内,只能有一次触发事件的回调函数执行,如果在同一个单位工夫内某事件被触发屡次,只有一次能失效。节流能够应用在 scroll 函数的事件监听上,通过事件节流来升高事件调用的频率。
// 函数节流的实现;
function throttle(fn, delay) {let curTime = Date.now();
return function() {
let context = this,
args = arguments,
nowTime = Date.now();
// 如果两次工夫距离超过了指定工夫,则执行函数。if (nowTime - curTime >= delay) {curTime = Date.now();
return fn.apply(context, args);
}
};
}
实现 Event(event bus)
event bus 既是 node 中各个模块的基石,又是前端组件通信的依赖伎俩之一,同时波及了订阅 - 公布设计模式,是十分重要的根底。
简略版:
class EventEmeitter {constructor() {this._events = this._events || new Map(); // 贮存事件 / 回调键值对
this._maxListeners = this._maxListeners || 10; // 设立监听下限
}
}
// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
// 从贮存事件键值对的 this._events 中获取对应事件回调函数
handler = this._events.get(type);
if (args.length > 0) {handler.apply(this, args);
} else {handler.call(this);
}
return true;
};
// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {
// 将 type 事件以及对应的 fn 函数放入 this._events 中贮存
if (!this._events.get(type)) {this._events.set(type, fn);
}
};
面试版:
class EventEmeitter {constructor() {this._events = this._events || new Map(); // 贮存事件 / 回调键值对
this._maxListeners = this._maxListeners || 10; // 设立监听下限
}
}
// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
// 从贮存事件键值对的 this._events 中获取对应事件回调函数
handler = this._events.get(type);
if (args.length > 0) {handler.apply(this, args);
} else {handler.call(this);
}
return true;
};
// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {
// 将 type 事件以及对应的 fn 函数放入 this._events 中贮存
if (!this._events.get(type)) {this._events.set(type, fn);
}
};
// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
handler = this._events.get(type);
if (Array.isArray(handler)) {
// 如果是一个数组阐明有多个监听者, 须要顺次此触发外面的函数
for (let i = 0; i < handler.length; i++) {if (args.length > 0) {handler[i].apply(this, args);
} else {handler[i].call(this);
}
}
} else {
// 单个函数的状况咱们间接触发即可
if (args.length > 0) {handler.apply(this, args);
} else {handler.call(this);
}
}
return true;
};
// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {const handler = this._events.get(type); // 获取对应事件名称的函数清单
if (!handler) {this._events.set(type, fn);
} else if (handler && typeof handler === "function") {
// 如果 handler 是函数阐明只有一个监听者
this._events.set(type, [handler, fn]); // 多个监听者咱们须要用数组贮存
} else {handler.push(fn); // 曾经有多个监听者, 那么间接往数组里 push 函数即可
}
};
EventEmeitter.prototype.removeListener = function(type, fn) {const handler = this._events.get(type); // 获取对应事件名称的函数清单
// 如果是函数, 阐明只被监听了一次
if (handler && typeof handler === "function") {this._events.delete(type, fn);
} else {
let postion;
// 如果 handler 是数组, 阐明被监听屡次要找到对应的函数
for (let i = 0; i < handler.length; i++) {if (handler[i] === fn) {postion = i;} else {postion = -1;}
}
// 如果找到匹配的函数, 从数组中革除
if (postion !== -1) {
// 找到数组对应的地位, 间接革除此回调
handler.splice(postion, 1);
// 如果革除后只有一个函数, 那么勾销数组, 以函数模式保留
if (handler.length === 1) {this._events.set(type, handler[0]);
}
} else {return this;}
}
};
实现具体过程和思路见实现 event
手写 call 函数
call 函数的实现步骤:
- 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window。
- 解决传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 应用上下文对象来调用这个办法,并保留返回后果。
- 删除方才新增的属性。
- 返回后果。
// call 函数实现
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的办法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
实现非负大整数相加
JavaScript 对数值有范畴的限度,限度如下:
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_VALUE // 5e-324
Number.MIN_SAFE_INTEGER // -9007199254740991
如果想要对一个超大的整数 (> Number.MAX_SAFE_INTEGER
) 进行加法运算,然而又想输入个别模式,那么应用 + 是无奈达到的,一旦数字超过 Number.MAX_SAFE_INTEGER
数字会被立刻转换为迷信计数法,并且数字精度相比以前将会有误差。
实现一个算法进行大数的相加:
function sumBigNumber(a, b) {
let res = '';
let temp = 0;
a = a.split('');
b = b.split('');
while (a.length || b.length || temp) {temp += ~~a.pop() + ~~b.pop();
res = (temp % 10) + res;
temp = temp > 9
}
return res.replace(/^0+/, '');
}
其次要的思路如下:
- 首先用字符串的形式来保留大数,这样数字在数学示意上就不会发生变化
- 初始化 res,temp 来保留两头的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
- 将两个数组的对应的位进行相加,两个数相加的后果可能大于 10,所以可能要仅为,对 10 进行取余操作,将后果保留在以后位
- 判断以后位是否大于 9,也就是是否会进位,若是则将 temp 赋值为 true,因为在加法运算中,true 会主动隐式转化为 1,以便于下一次相加
- 反复上述操作,直至计算完结
实现 add(1)(2)(3)
函数柯里化概念:柯里化(Currying)是把承受多个参数的函数转变为承受一个繁多参数的函数,并且返回承受余下的参数且返回后果的新函数的技术。
1)粗犷版
function add (a) {return function (b) {return function (c) {return a + b + c;}
}
}
console.log(add(1)(2)(3)); // 6
2)柯里化解决方案
- 参数长度固定
var add = function (m) {var temp = function (n) {return add(m + n);
}
temp.toString = function () {return m;}
return temp;
};
console.log(add(3)(4)(5)); // 12
console.log(add(3)(6)(9)(25)); // 43
对于 add(3)(4)(5),其执行过程如下:
- 先执行 add(3),此时 m =3,并且返回 temp 函数;
- 执行 temp(4),这个函数内执行 add(m+n),n 是此次传进来的数值 4,m 值还是上一步中的 3,所以 add(m+n)=add(3+4)=add(7),此时 m =7,并且返回 temp 函数
- 执行 temp(5),这个函数内执行 add(m+n),n 是此次传进来的数值 5,m 值还是上一步中的 7,所以 add(m+n)=add(7+5)=add(12),此时 m =12,并且返回 temp 函数
- 因为前面没有传入参数,等于返回的 temp 函数不被执行而是打印,理解 JS 的敌人都晓得对象的 toString 是批改对象转换字符串的办法,因而代码中 temp 函数的 toString 函数 return m 值,而 m 值是最初一步执行函数时的值 m =12,所以返回值是 12。
- 参数长度不固定
function add (...args) {
// 求和
return args.reduce((a, b) => a + b)
}
function currying (fn) {let args = []
return function temp (...newArgs) {if (newArgs.length) {
args = [
...args,
...newArgs
]
return temp
} else {let val = fn.apply(this, args)
args = [] // 保障再次调用时清空
return val
}
}
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4, 5)()) //15
console.log(addCurry(1)(2)(3, 4, 5)()) //15
console.log(addCurry(1)(2, 3, 4, 5)()) //15
实现日期格式化函数
输出:
dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy 年 MM 月 dd 日') // 2020 年 04 月 01 日
const dateFormat = (dateInput, format)=>{var day = dateInput.getDate()
var month = dateInput.getMonth() + 1
var year = dateInput.getFullYear()
format = format.replace(/yyyy/, year)
format = format.replace(/MM/,month)
format = format.replace(/dd/,day)
return format
}
字符串查找
请应用最根本的遍从来实现判断字符串 a 是否被蕴含在字符串 b 中,并返回第一次呈现的地位(找不到返回 -1)。
a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);
function isContain(a, b) {for (let i in b) {if (a[0] === b[i]) {
let tmp = true;
for (let j in a) {if (a[j] !== b[~~i + ~~j]) {tmp = false;}
}
if (tmp) {return i;}
}
}
return -1;
}
Function.prototype.call
于 call
惟一不同的是,call()
办法承受的是一个参数列表
Function.prototype.call = function(context = window, ...args) {if (typeof this !== 'function') {throw new TypeError('Type Error');
}
const fn = Symbol('fn');
context[fn] = this;
const res = context[fn](...args);
delete context[fn];
return res;
}
模板引擎实现
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄 18,性别 undefined
function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的构造
}
return template; // 如果模板没有模板字符串间接返回
}
封装异步的 fetch,应用 async await 形式来应用
(async () => {
class HttpRequestUtil {async get(url) {const res = await fetch(url);
const data = await res.json();
return data;
}
async post(url, data) {
const res = await fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async put(url, data) {
const res = await fetch(url, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async delete(url, data) {
const res = await fetch(url, {
method: 'DELETE',
headers: {'Content-Type': 'application/json'},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
}
const httpRequestUtil = new HttpRequestUtil();
const res = await httpRequestUtil.get('http://golderbrother.cn/');
console.log(res);
})();
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();})
}