1.手写 Object.create
function customObjectCreate(proto, propertiesObject) {
// 判断传入的 proto 是否为 null 或者对象
if (proto !== Object(proto) && proto !== null) {
throw new TypeError('Object prototype may only be an Object or null.');
}
// 创建一个临时的构造函数
function F() {}
// 将临时构造函数的原型设置为传入的 proto
F.prototype = proto;
// 创建一个继承了 proto 的新对象
const obj = new F();
// 如果传入了 propertiesObject,定义其属性
if (propertiesObject !== undefined) {
Object.defineProperties(obj, propertiesObject);
}
// 返回新创建的对象
return obj;
}
// 使用方法
// 定义原型对象
const person = {
isHuman: false,
printIntroduction: function() {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
// 定义属性描述符对象
const properties = {
name: {
value: 'John Doe',
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 30,
writable: false,
enumerable: true,
configurable: false
},
greet: {
value: function() {
console.log(`Hello, my name is ${this.name}`);
},
writable: false,
enumerable: false,
configurable: true
}
};
// 创建新对象
const me = customObjectCreate(person, properties);
// 测试新对象
me.printIntroduction(); // 输出: My name is John Doe. Am I human? false
console.log(me.name); // 输出: John Doe
console.log(me.age); // 输出: 30
me.greet(); // 输出: Hello, my name is John Doe
propertiesObject
参数的格式
propertiesObject
是一个对象,其中每个键代表一个属性名,每个值是一个属性描述符对象。属性描述符对象可以包含以下属性:
value
: 属性的值。writable
: 如果为true
,则属性值可以被修改(默认为false
)。enumerable
: 如果为true
,则属性会出现在对象的枚举属性列表中(默认为false
)。configurable
: 如果为true
,则属性可以被删除或修改其特性(默认为false
)。get
: 一个函数,用于获取属性值(如果有value
,则get
和value
不能同时存在)。set
: 一个函数,用于设置属性值(如果有value
,则set
和value
不能同时存在)。
2.手写 instanceof 方法
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left); // 获取对象的原型
let prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false; // 如果原型链结束(proto 为 null),返回 false
if (proto === prototype) return true; // 如果原型链中找到构造函数的 prototype,返回 true
proto = Object.getPrototypeOf(proto); // 向上移动到原型链的上一级
}
}
// 使用示例
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
console.log(myInstanceof(alice, Person)); // 输出: true
console.log(myInstanceof(alice, Object)); // 输出: true
console.log(myInstanceof(alice, Array)); // 输出: false
3.手写 new 操作符
function customNew(constructor, ...args) {
// 1. 创建一个新对象
const obj = {};
// 2. 设置新对象的原型为构造函数的 prototype
Object.setPrototypeOf(obj, constructor.prototype);
// 3. 将构造函数的 `this` 绑定到新对象上,并执行构造函数
const result = constructor.apply(obj, args);
// 4. 如果构造函数返回的是对象,则返回这个对象
// 否则返回新创建的对象
return result instanceof Object ? result : obj;
}
// 使用示例
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
const alice = customNew(Person, 'Alice', 30);
console.log(alice.name); // 输出: Alice
console.log(alice.age); // 输出: 30
alice.sayHello(); // 输出: Hello, my name is Alice
result instanceof Object
为false
的情况很少,但可能发生在以下情况:
构造函数返回原始值(非对象): 如果构造函数返回的是原始值,如
number
,string
,boolean
,null
或undefined
,那么result instanceof Object
会返回false
。例如:function MyConstructor() { return 42; // 返回原始值 } const instance = customNew(MyConstructor); console.log(instance instanceof Object); // false
构造函数返回一个不是
Object
的实例: 如果构造函数返回的是一个自定义的特殊类型或构造函数的实例,但它不是一个标准的Object
(例如某些非标准的对象),instanceof Object
也会返回false
。这很少见,因为大多数 JavaScript 对象都是从Object
构造函数派生的。function MyConstructor() { return new MySpecialType(); // MySpecialType 不是标准的 Object 类型 } function MySpecialType() {} const instance = customNew(MyConstructor); console.log(instance instanceof Object); // false
构造函数返回
null
或undefined
: 如果构造函数返回null
或undefined
,result instanceof Object
会返回false
。function MyConstructor() { return null; // 返回 null } const instance = customNew(MyConstructor); console.log(instance instanceof Object); // false
通常情况下,构造函数返回
Object
实例或其子类(如Array
,Function
,Date
等),因此result instanceof Object
多数时间会为true
。确保构造函数按预期返回对象通常是确保自定义new
实现正确性的关键。
4.使用 Promise
封装 AJAX 请求
function ajax(url, options = {}) {
return new Promise((resolve, reject) => {
// 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();
// 配置请求类型和 URL
xhr.open(options.method || 'GET', url);
// 设置请求头
if (options.headers) {
Object.keys(options.headers).forEach(key => {
xhr.setRequestHeader(key, options.headers[key]);
});
}
// 设置请求的响应类型
if (options.responseType) {
xhr.responseType = options.responseType;
}
// 处理请求状态变化
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
// 请求成功,解析并返回响应数据
resolve(xhr.response);
} else {
// 请求失败,返回错误信息
reject(new Error(`HTTP Error: ${xhr.status}`));
}
}
};
// 处理请求错误
xhr.onerror = () => {
reject(new Error('Network Error'));
};
// 发送请求
xhr.send(options.body || null);
});
}
// 使用示例
ajax('https://jsonplaceholder.typicode.com/posts', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
console.log('Success:', response);
})
.catch(error => {
console.error('Error:', error);
});
readyState
属性
readyState
属性表示XMLHttpRequest
对象的当前状态,取值范围是 0 到 4,分别表示不同的请求阶段:
0 (UNSENT)
:
- 请求对象已创建,但尚未调用
open()
方法。请求未被初始化。
1 (OPENED)
:
open()
方法已被调用,请求已经被初始化,但尚未发送请求。此时可以设置请求头和请求体。
2 (HEADERS_RECEIVED)
:
- 请求已发送,响应头已接收。此时可以访问响应头,但响应体尚未完全接收。
3 (LOADING)
:
- 响应体正在接收中。此时可以访问部分响应体数据,但响应尚未完全接收。
4 (DONE)
:
- 请求已完成,响应体已经完全接收。此时可以访问完整的响应数据和状态码。
status
属性
status
属性表示响应的 HTTP 状态码,通常用于确定请求是否成功。常见的状态码包括:
200 OK
:
- 请求成功,服务器返回请求的数据。
201 Created
:
- 请求成功,并且创建了新的资源。
204 No Content
:
- 请求成功,但没有返回任何内容。
400 Bad Request
:
- 请求无效或存在语法错误,服务器无法理解。
401 Unauthorized
:
- 请求未经授权,需进行身份验证。
403 Forbidden
:
- 服务器拒绝执行请求。
404 Not Found
:
- 请求的资源未找到。
500 Internal Server Error
:
- 服务器内部错误,无法完成请求。
502 Bad Gateway
:
- 网关错误,服务器从上游服务器接收到无效响应。
XMLHttpRequest
常量及其含义
XMLHttpRequest.UNSENT (0)
- 含义: 请求对象已创建,但尚未调用
open()
方法。此时请求未被初始化。
XMLHttpRequest.OPENED (1)
- 含义:
open()
方法已被调用,表示请求已经初始化,但尚未发送请求。此时可以设置请求头和请求体。
XMLHttpRequest.HEADERS_RECEIVED (2)
- 含义: 请求已发送,服务器响应头已接收。此时可以访问响应头,但响应体尚未完全接收。
XMLHttpRequest.LOADING (3)
- 含义: 响应体正在接收中。此时可以访问部分响应体数据,但响应尚未完全接收。
XMLHttpRequest.DONE (4)
- 含义: 请求已完成,无论请求成功还是失败。此时可以访问完整的响应数据和状态码。
5.类型判断函数
function getType(value) {
// 判断数据是 null 的情况
if (value === null) {
return 'null';
}
// 判断数据是引用类型的情况
if (typeof value === "object") {
const valueClass = Object.prototype.toString.call(value);
const type = valueClass.slice(8, -1); // 提取 `[object Type]` 中的 `Type`
return type.toLowerCase(); // 转为小写
}
// 判断数据是基本数据类型的情况和函数的情况
return typeof value;
}
// 测试
console.log(getType(123)); // 'number'
console.log(getType('Hello')); // 'string'
console.log(getType(true)); // 'boolean'
console.log(getType(undefined)); // 'undefined'
console.log(getType(null)); // 'null'
console.log(getType([1, 2, 3])); // 'array'
console.log(getType({ key: 'value' })); // 'object'
console.log(getType(() => {})); // 'function'
console.log(getType(new Date())); // 'date'
console.log(getType(/regex/)); // 'regexp'
解释
null
的处理:
- 如果
value
是null
,直接返回'null'
。引用类型的处理:
- 使用
Object.prototype.toString.call(value)
返回[object Type]
形式的字符串。- 使用
slice(8, -1)
从字符串中提取类型名称,如'Array'
、'Date'
等。这里的8
是因为"[object "
的长度,-1
去掉了末尾的"]"
。- 将类型名称转换为小写以保持一致性。
基本数据类型和函数:
- 对于基本数据类型(
number
、string
、boolean
、undefined
)以及function
,直接使用typeof
进行判断。总结
这个优化后的
getType
函数更简洁且易于理解,同时保留了Object.prototype.toString.call
的优点,能够准确识别多种类型,包括Array
、Date
和RegExp
。
6.函数柯里化
函数柯里化(Currying)是将一个接受多个参数的函数转换成一个接受一个参数的函数,并返回一个接受下一个参数的函数,直到所有参数都被接受并且函数执行。
实现科里化函数
我们可以实现一个通用的科里化函数,这个函数会接受一个原始函数,并返回一个可以逐步接受参数的函数,直到所有参数都被提供为止。
//方法一
const add(x) {
return function (y) {
return function (z) {
return x + y + z
}
}
}
console.log(add(1)(2)(3));
//方法二
function curry(fn) {
return function (y) {
return function (z) {
return fn(x, y, z);
};
};
}
var add = curry((x, y, z) => {
return x + y + z;
});
console.log(add(1)(2)(3)); // 6
通用科里化函数的实现
function curry(fn) {
const expectedArgsLength = fn.length; // 期望的参数数量
// 返回一个内部函数来处理参数
function curried(...args) {
// 如果当前参数数量少于期望数量,继续返回一个新的函数
if (args.length >= expectedArgsLength) {
// 调用原函数,并传递参数
return fn(...args);
}
// 否则,返回一个新的函数来继续接受参数
return (...moreArgs) => curried(...args, ...moreArgs);
}
return curried;
}
示例:应用科里化
假设我们有一个函数 multiply
,它接受多个参数,并对它们进行乘法计算。我们可以使用科里化将其转化为逐步接受参数的形式。
// 定义一个多参数函数
function multiply(a, b, c) {
return a * b * c;
}
// 使用科里化函数包装 multiply
const curriedMultiply = curry(multiply);
// 使用科里化函数逐步传递参数
console.log(curriedMultiply(2)(3)(4)); // 输出: 24
// 也可以将参数一次性传递
console.log(curriedMultiply(2, 3, 4)); // 输出: 24
科里化应用场景
科里化可以用于许多不同的场景,不仅仅是计算数据和。例如:
-
配置函数:
- 对于需要多个配置选项的函数,科里化可以让你逐步设置这些配置。
function createUser(name, age, address) { return { name, age, address }; } const curriedCreateUser = curry(createUser); const user = curriedCreateUser('Alice')(30)('123 Main St'); console.log(user); // 输出: { name: 'Alice', age: 30, address: '123 Main St' }
-
事件处理:
- 对于需要多个处理参数的事件处理函数,科里化可以逐步设置这些参数。
function handleEvent(type, details, callback) { console.log(`Event Type: ${type}`); console.log(`Event Details: ${details}`); callback(); } const curriedHandleEvent = curry(handleEvent); curriedHandleEvent('click')('button clicked')(() => console.log('Event handled'));
-
函数组合:
- 将多个函数组合成一个管道(pipeline),使得每个函数的输入都是前一个函数的输出。
function add(a, b) { return a + b; } function multiply(a, b) { return a * b; } const curriedAdd = curry(add); const curriedMultiply = curry(multiply); const result = curriedMultiply(2)(curriedAdd(3)(4)); // 2 * (3 + 4) = 14 console.log(result); // 输出: 14
总结
- 通用科里化:使用
curry
函数可以将任何接受多个参数的函数转化为逐步接受参数的形式。 - 应用场景:科里化不仅限于简单的数据计算,也可以应用于配置、事件处理、函数组合等多个场景。
7.递归实现深拷贝
function deepClone(obj, map = new WeakMap()) {
// 基本数据类型和函数直接返回
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (map.has(obj)) {
return map.get(obj);
}
// 创建一个新的对象或数组
const clone = Array.isArray(obj) ? [] : {};
// 将当前对象存储在 map 中,以处理循环引用
map.set(obj, clone);
// 递归拷贝对象的所有属性
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
8.数组扁平化
1. 使用递归方法
递归是最直观的方式,通过遍历数组的每个元素,如果元素是数组,则递归地将其展开。
function flattenArray(arr) {
let result = [];
arr.forEach(item => {
if (Array.isArray(item)) {
result = result.concat(flattenArray(item)); // 如果元素是数组,递归处理
} else {
result.push(item); // 如果元素不是数组,直接加入结果
}
});
return result;
}
const nestedArray = [1, [2, [3, [4]], 5], 6];
console.log(flattenArray(nestedArray)); // 输出: [1, 2, 3, 4, 5, 6]
2. 使用 ES6 reduce()
方法
reduce()
方法可以用来将数组的每个元素逐步处理,并将结果累积起来。
function flattenArray(arr) {
return arr.reduce((acc, item) => {
if (Array.isArray(item)) {
return acc.concat(flattenArray(item)); // 如果元素是数组,递归处理并累积结果
} else {
return acc.concat(item); // 如果元素不是数组,直接累积到结果中
}
}, []);
}
const nestedArray = [1, [2, [3, [4]], 5], 6];
console.log(flattenArray(nestedArray)); // 输出: [1, 2, 3, 4, 5, 6]
3. 使用 ES6 flat()
方法
从 ECMAScript 2019(ES10)开始,JavaScript 引入了 Array.prototype.flat()
方法,它可以直接将数组扁平化。
const nestedArray = [1, [2, [3, [4]], 5], 6];
const flatArray = nestedArray.flat(Infinity); // 使用 Infinity 展开任意深度的数组
console.log(flatArray); // 输出: [1, 2, 3, 4, 5, 6]
4. 使用 while
循环和扩展运算符(...
)
这是另一种通过非递归方式实现数组扁平化的方法。
function flattenArray(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr); // 使用扩展运算符将数组展开一层
}
return arr;
}
const nestedArray = [1, [2, [3, [4]], 5], 6];
console.log(flattenArray(nestedArray)); // 输出: [1, 2, 3, 4, 5, 6]
9.实现数组去重
1. 使用 Set
Set
是 ES6 引入的一个数据结构,它类似于数组,但不允许有重复的值。可以利用 Set
的这个特性来轻松实现数组去重。
function uniqueArray(arr) {
return [...new Set(arr)];
}
const array = [1, 2, 3, 4, 3, 2, 1];
console.log(uniqueArray(array)); // 输出: [1, 2, 3, 4]
2. 使用 filter
和 indexOf
filter
方法可以用来筛选数组中的元素,结合 indexOf
可以实现去重功能。
function uniqueArray(arr) {
return arr.filter((value, index) => arr.indexOf(value) === index);
}
const array = [1, 2, 3, 4, 3, 2, 1];
console.log(uniqueArray(array)); // 输出: [1, 2, 3, 4]
3. 使用 reduce
reduce
方法可以通过累积器(accumulator)来构建一个不包含重复元素的新数组。
function uniqueArray(arr) {
return arr.reduce((acc, value) => {
if (!acc.includes(value)) {
acc.push(value);
}
return acc;
}, []);
}
const array = [1, 2, 3, 4, 3, 2, 1];
console.log(uniqueArray(array)); // 输出: [1, 2, 3, 4]
4. 使用 Map
可以使用 Map
来记录数组中的元素是否出现过,遍历数组并根据 Map
记录进行去重。
function uniqueArray(arr) {
const map = new Map();
return arr.filter(value => !map.has(value) && map.set(value, true));
}
const array = [1, 2, 3, 4, 3, 2, 1];
console.log(uniqueArray(array)); // 输出: [1, 2, 3, 4]
5. 使用 forEach
和对象作为哈希表
你可以使用一个对象来记录已经出现过的元素,然后使用 forEach
来遍历数组,筛选出唯一的元素。
function uniqueArray(arr) {
const result = [];
const seen = {};
arr.forEach(value => {
if (!seen[value]) {
seen[value] = true;
result.push(value);
}
});
return result;
}
const array = [1, 2, 3, 4, 3, 2, 1];
console.log(uniqueArray(array)); // 输出: [1, 2, 3, 4]
10.将数字每千分位用逗号隔开
const number = 1234567.89;
const formattedNumber = number.toLocaleString();
console.log(formattedNumber); // 输出: "1,234,567.89" (在大多数英语环境中)
11.使用 reduce 求和
const items = [
{ price: 10 },
{ price: 20 },
{ price: 30 }
];
const totalPrice = items.reduce((acc, item) => acc + item.price, 0);
console.log(totalPrice); // 输出: 60
12.将js对象转化为树形结
const flatData = [
{ id: 1, name: 'A', parentId: null },
{ id: 2, name: 'B', parentId: 1 },
{ id: 3, name: 'C', parentId: 1 },
{ id: 4, name: 'D', parentId: 2 },
{ id: 5, name: 'E', parentId: 2 },
{ id: 6, name: 'F', parentId: 3 }
];
//转换成下面的结构
[
{
"id": 1,
"name": "A",
"parentId": null,
"children": [
{
"id": 2,
"name": "B",
"parentId": 1,
"children": [
{
"id": 4,
"name": "D",
"parentId": 2,
"children": []
},
{
"id": 5,
"name": "E",
"parentId": 2,
"children": []
}
]
},
{
"id": 3,
"name": "C",
"parentId": 1,
"children": [
{
"id": 6,
"name": "F",
"parentId": 3,
"children": []
}
]
}
]
}
]
function buildTree(data) {
const map = {};
const tree = [];
// 创建映射:每个节点的 ID 作为键,节点对象作为值
data.forEach(item => {
map[item.id] = { ...item, children: [] };
});
// 构建树结构
data.forEach(item => {
if (item.parentId === null) {
// 如果没有父节点,作为根节点
tree.push(map[item.id]);
} else {
// 将子节点添加到其父节点的 children 属性中
if (map[item.parentId]) {
map[item.parentId].children.push(map[item.id]);
}
}
});
return tree;
}
const treeData = buildTree(flatData);
console.log(JSON.stringify(treeData, null, 2));
13.解析 URL Params 为对象
function parseParam(url) {
const params = new URLSearchParams(new URL(url).search);
const result = {};
for (const [key, value] of params.entries()) {
// 处理重复的键
if (result.hasOwnProperty(key)) {
// 如果结果对象中已存在该键,则将其转换为数组或推入数组中
if (Array.isArray(result[key])) {
result[key].push(isNaN(value) ? value : Number(value));
} else {
result[key] = [result[key], isNaN(value) ? value : Number(value)];
}
} else {
// 处理键的值
result[key] = isNaN(value) ? decodeURIComponent(value) : Number(value);
}
}
// 处理未指定值的键
for (const key of new URL(url).searchParams.keys()) {
if (params.get(key) === null) {
result[key] = true;
}
}
return result;
}
// 示例使用
const url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
console.log(parseParam(url));
14.循环打印红黄绿
function red(callback) {
console.log('red');
setTimeout(callback, 3000); // 红灯亮 3 秒
}
function green(callback) {
console.log('green');
setTimeout(callback, 1000); // 绿灯亮 1 秒
}
function yellow(callback) {
console.log('yellow');
setTimeout(callback, 2000); // 黄灯亮 2 秒
}
function startCycle() {
red(() => {
green(() => {
yellow(startCycle); // 在黄灯亮完后,递归调用 startCycle 继续循环
});
});
}
startCycle();
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function red() {
console.log('red');
return delay(3000); // 红灯亮 3 秒
}
function green() {
console.log('green');
return delay(1000); // 绿灯亮 1 秒
}
function yellow() {
console.log('yellow');
return delay(2000); // 黄灯亮 2 秒
}
async function startCycle() {
while (true) {
await red();
await green();
await yellow();
}
}
startCycle();
15.实现每隔一秒打印 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);
}
16.使用 setTimeout 实现 setInterval
function customSetInterval(callback, interval) {
function intervalFunction() {
callback(); // 执行回调函数
setTimeout(intervalFunction, interval); // 递归调用,设置下一次的超时
}
setTimeout(intervalFunction, interval); // 启动第一次调用
}
// 使用示例
customSetInterval(() => {
console.log('This message prints every 1000 milliseconds');
}, 1000);
function customSetInterval(callback, interval) {
let timerId;
function intervalFunction() {
callback(); // 执行回调函数
timerId = setTimeout(intervalFunction, interval); // 递归调用,设置下一次的超时
}
timerId = setTimeout(intervalFunction, interval); // 启动第一次调用
return function clearCustomInterval() {
clearTimeout(timerId); // 清除定时器
};
}
// 使用示例
const stopInterval = customSetInterval(() => {
console.log('This message prints every 1000 milliseconds');
}, 1000);
// 停止定时器
setTimeout(() => {
stopInterval(); // 停止自定义的 setInterval
}, 5000);