实现 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
复制代码
手写深度比较 isEqual
思路:深度比较两个对象,就是要深度比较对象的每一个元素。=> 递归
function isEqual(obj1, obj2){
//其中一个为值类型或null
if(!isObject(obj1) || !isObject(obj2)){
return obj1 === obj2;
}
//判断是否两个参数是同一个变量
if(obj1 === obj2){
return true;
}
//判断keys数是否相等
const obj1Keys = Object.keys(obj1);
const obj2Keys = Object.keys(obj2);
if(obj1Keys.length !== obj2Keys.length){
return false;
}
//深度比较每一个key
for(let key in obj1){
if(!isEqual(obj1[key], obj2[key])){
return false;
}
}
return true;
}
复制代码
实现数组的 filter 方法
Array.prototype._filter = function(fn) {
if (typeof fn !== "function") {
throw Error('参数必须是一个函数');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {
fn(this[i]) && res.push(this[i]);
}
return res;
}
复制代码
实现字符串翻转
在字符串的原型链上添加一个方法,实现字符串翻转:
String.prototype._reverse = function(a){
return a.split("").reverse().join("");
}
var obj = new String();
var res = obj._reverse ('hello');
console.log(res); // olleh
复制代码
需要注意的是,必须通过实例化对象之后再去调用定义的方法,不然找不到该方法。
实现每隔一秒打印 1,2,3,4
// 使用闭包实现
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
// 使用 let 块级作用域
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
复制代码
Object.assign
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,
})
复制代码
参考 前端进阶面试题详细解答
封装异步的 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);
})();
复制代码
查找文章中出现频率最高的单词
function findMostWord(article) {
// 合法性判断
if (!article) return;
// 参数处理
article = article.trim().toLowerCase();
let wordList = article.match(/[a-z]+/g),
visited = [],
maxNum = 0,
maxWord = "";
article = " " + wordList.join(" ") + " ";
// 遍历判断单词出现次数
wordList.forEach(function(item) {
if (visited.indexOf(item) < 0) {
// 加入 visited
visited.push(item);
let word = new RegExp(" " + item + " ", "g"),
num = article.match(word).length;
if (num > maxNum) {
maxNum = num;
maxWord = item;
}
}
});
return maxWord + " " + maxNum;
}
复制代码
实现浅拷贝
浅拷贝是指,一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生变化。
(1)Object.assign()
Object.assign()
是 ES6 中对象的拷贝方法,接受的第一个参数是目标对象,其余参数是源对象,用法:Object.assign(target, source_1, ···)
,该方法可以实现浅拷贝,也可以实现一维对象的深拷贝。
注意:
如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
因为null
和 undefined
不能转化为对象,所以第一个参数不能为null
或 undefined
,会报错。
let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);
console.log(target); // {a: 1, b: 2, c: 3}
复制代码
(2)扩展运算符
使用扩展运算符可以在构造字面量对象的时候,进行属性的拷贝。语法:let cloneObj = { ...obj };
let obj1 = {a:1,b:{c:1}}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}
复制代码
(3)数组方法实现数组浅拷贝
1)Array.prototype.slice
slice()
方法是 JavaScript 数组的一个方法,这个方法可以从已有数组中返回选定的元素:用法:array.slice(start, end)
,该方法不会改变原始数组。
该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。
let arr = [1,2,3,4];
console.log(arr.slice()); // [1,2,3,4]
console.log(arr.slice() === arr); //false
复制代码
2)Array.prototype.concat
let arr = [1,2,3,4];
console.log(arr.concat()); // [1,2,3,4]
console.log(arr.concat() === arr); //false
复制代码
(4)手写实现浅拷贝
// 浅拷贝的实现;
function shallowCopy(object) {
// 只拷贝对象
if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象
let newObject = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
return newObject;
}// 浅拷贝的实现;
function shallowCopy(object) {
// 只拷贝对象
if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象
let newObject = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
return newObject;
}// 浅拷贝的实现;
function shallowCopy(object) {
// 只拷贝对象
if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象
let newObject = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
return newObject;
}
复制代码
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();
})
}
复制代码
实现千位分隔符
// 保留三位小数
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'
复制代码
function parseToMoney(num) {
num = parseFloat(num.toFixed(3));
let [integer, decimal] = String.prototype.split.call(num, '.');
integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');
return integer + '.' + (decimal ? decimal : '');
}
复制代码
正则表达式(运用了正则的前向声明和反前向声明):
function parseToMoney(str){
// 仅仅对位置进行匹配
let re = /(?=(?!\b)(\d{3})+$)/g;
return str.replace(re,',');
}
复制代码
Function.prototype.bind
Function.prototype.bind = function(context, ...args) {
if (typeof this !== 'function') {
throw new Error("Type Error");
}
// 保存this的值
var self = this;
return function F() {
// 考虑new的情况
if(this instanceof F) {
return new self(...args, ...arguments)
}
return self.apply(context, [...args, ...arguments])
}
}
复制代码
实现数组的 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;
}
复制代码
实现数组去重
给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。
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;
}
复制代码
实现节流函数(throttle)
防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
// 手写简化版
// 节流函数
const throttle = (fn, delay = 500) => {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
};
};
复制代码
适用场景:
模板引擎实现
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; // 如果模板没有模板字符串直接返回
}
复制代码
实现 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__;
}
}
复制代码
实现数组的 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);
}
}, []);
}
复制代码
判断是否是电话号码
function isPhone(tel) {
var regx = /^1[34578]\d{9}$/;
return regx.test(tel);
}
复制代码
模拟 new
new 操作符做了这些事:
它创建了一个全新的对象
它会被执行[[Prototype]](也就是__proto__)链接
它使 this 指向新创建的对象
通过 new 创建的每个对象将最终被[[Prototype]]链接到这个函数的 prototype 对象上
如果函数没有返回对象类型 Object(包含 Functoin, Array, Date, RegExg, Error),那么 new 表达式中的函数调用将返回该对象引用
// objectFactory(name, 'cxk', '18')
function objectFactory() {
const obj = new Object();
const Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
const ret = Constructor.apply(obj, arguments);
return typeof ret === "object" ? ret : obj;
}
复制代码
评论