对于Web开发人员来说,理解和掌握JavaScript是非常重要的,JavaScript 开发者常常会遇到一系列手写代码的挑战,旨在测试开发者的语言掌握程度、编程技巧以及解决问题的能力。本文将介绍一些常见的JavaScript手写题,以及它们的简要解析。
一、实现一个函数计算斐波那契数列
1、用for循环来实现,时间复杂度为O(n),空间复杂度为O(1)
function fibonacci2(n) {
if (n <= 1) return n;
let a = 0;
let b = 1;
for (let i = 2; i <= n; i++) {
let temp = a + b;
a = b;
b = temp;
}
return b;
}
2、优雅一点的写法
const cache = [0, 1];
function fabonacci(n) {
return typeof cache[n] === 'number' ? cache[n] : cache[n] = fabonacci(n - 1) + fabonacci(n - 2);
}
二、实现数组的map方法
function myMap(arr, callback) {
// 检查参数是否正确
if (!Array.isArray(arr) || !arr.length || typeof callback !== 'function') {
return [];
} else {
let result = [];
for (let i = 0, len = arr.length; i < len; i++) {
result.push(callback(arr[i], i, arr))
}
return result
}
}
三、实现数组的map方法
又叫质数,在大于1的自然数中,除了1和它本身以外不再有其他因数。要判断一个数是否是素数,最直观的方法就是将这个数从2开始,一直到该数的平方根的所有数中,分别进行取余运算。如果有任意一个数可以整除这个数字,那么它就不是素数。
function isPrime(num) {
//判断数字是否大于1
if (num <= 1) {
return false;
}
//从2开始,一直到该数的平方根的所有数中,分别进行取余运算
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) {
return false;
}
}
return true;
}
四、实现instanceof
如果 target 为基本数据类型直接返回 false;判断 Fn.prototype 是否在 target 的隐式原型链上。
const _instanceof = (target, Fn) => {
if ((typeof target !== 'object' && typeof target !== 'function') || target === null)
return false;
let proto = target.__proto__
while (true) {
if (proto === null) return false
if (proto === Fn.prototype) return true
proto = proto.__proto__
}
}
function A() {}
const a = new A()
console.log(_instanceof(a, A)) // true
console.log(_instanceof(1, A)) // false
五、实现call方法
var name = '公众号:前端技术营';
var obj = {
name: '张三'
};
function fn(a, b, c) {
console.log(`${a} ${b} ${c} ${this.name}`);
};
// 模拟call
Function.prototype.myCall = function(){
let target = arguments[0]|| window || global
target.fn = this
let args = Array.from(arguments).slice(1)
let result = target.fn(...args)
delete target.fn
return result
}
fn.myCall(obj, 'my', 'name', 'is'); // my name is 张三
fn.myCall(null, 'my', 'name', 'is'); // my name is 公众号:前端技术营
fn.myCall(undefined, 'my', 'name', 'is'); // my name is 公众号:前端技术营
六、实现apply方法
var name = '公众号:前端技术营';
var obj = {
name: '张三'
};
function fn(a, b, c) {
console.log(`${a} ${b} ${c} ${this.name}`);
};
// 模拟apply
Function.prototype.myApply = function(){
let target = arguments[0]|| window || global;
target.fn = this;
let args = Array.from(arguments)[1];
let result = target.fn(...args);
delete target.fn;
return result;
}
fn.myApply(obj, ['my', 'name', 'is']); // my name is 张三
fn.myApply(null, ['my', 'name', 'is']); // my name is 公众号:前端技术营
fn.myApply(undefined, ['my', 'name', 'is']); // my name is 公众号:前端技术营
七、实现bind方法
var name = '公众号:前端技术营';
var obj = {
name: '张三'
};
function fn(a, b, c) {
console.log(`${a} ${b} ${c} ${this.name}`);
};
// 模拟call
Function.prototype.myBind = function(context) {
const _this = this
const args = [...arguments].slice(1);
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
// 忽略传入的this
return new _this(...args, ...arguments)
}
// 直接调用,将两边的参数拼接起来
return _this.apply(context, args.concat(...arguments))
}
}
const fn1 = fn.myBind(obj, 'my', 'name', 'is');
fn1(); // my name is 张三
const fn2 = fn.myBind(null, 'my', 'name', 'is');
fn2(); // my name is 公众号:前端技术营
const fn3 = fn.myBind(undefined, 'my', 'name', 'is');
fn3(); // my name is 公众号:前端技术营
八、实现new关键字
function myNew() {
// 创建一个空对象
let obj = new Object();
// shift删除数组第一个元素,并返回一个元素。原有arguments数组第一个参数是构造函数,返回值为构造函数。
const Constructor = [].shift.call(arguments);
// 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
obj.__proto__ = Constructor.prototype;
// 执行构造函数的方法
const ret = Constructor.apply(obj, arguments);
// 返回构造函数中有返回值且返回值是个引用类型,没有返回值则默认返回这个对象
return typeof ret === 'object' ? ret : obj;
}
function Person(name, age) {
this.name = name;
this.age = age
// 模拟有返回值且是个对象
return {
name: name,
address: '上海市浦东新区'
};
}
const a = myNew(Person, '公众号:前端技术营', 18);
console.log(a.name); // 公众号:前端技术营
console.log(a.address); // 上海市浦东新区
console.log(a.age); // undefined
九、实现函数柯里化
函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
function curry(fn, args) {
// 获取函数需要的参数长度
let length = fn.length;
args = args || [];
return function() {
let subArgs = args.slice(0);
// 拼接得到现有的所有参数
for (let i = 0; i < arguments.length; i++) {
subArgs.push(arguments[i]);
}
// 判断参数的长度是否已经满足函数所需参数的长度
if (subArgs.length >= length) {
// 如果满足,执行函数
return fn.apply(this, subArgs);
} else {
// 如果不满足,递归返回科里化的函数,等待参数的传入
return curry.call(this, fn, subArgs);
}
};
}
// es6 实现
function curry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
十、实现object.create()方法
function objectCreate(obj){
function F(){}; // 构造函数
F.prototype = obj; // obj 作为构造函数原型的属性
return new F(); // 返回一个实例对象 就像 `let ff = new F(); return ff`
}
十一、数组去重
// 1.利用Set
const res = Array.from(new Set(arr));
// 2.两层for循环+splice
const unique = arr => {
let len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
len--;
j--;
}
}
}
return arr;
}
// 3.利用indexOf
const unique = arr => {
const res = [];
for (let i = 0; i < arr.length; i++) {
if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
}
return res;
}
// 4.利用include
const unique = arr => {
const res = [];
for (let i = 0; i < arr.length; i++) {
if (!res.includes(arr[i])) res.push(arr[i]);
}
return res;
}
// 5.利用filter
const unique = arr => {
return arr.filter((item, index) => {
return arr.indexOf(item) === index;
});
}
// 6.利用Map
const unique = arr => {
const map = new Map();
const res = [];
for (let i = 0; i < arr.length; i++) {
if (!map.has(arr[i])) {
map.set(arr[i], true);
res.push(arr[i]);
}
}
return res;
}
十二、数组求和
// 1.利用for循环
function getSum(arr){
let sum = 0;
for(var i = 0;i<arr.length;i++){
sum += arr[i];
}
return sum ;
}
// 2.利用reduce
const sum = arr.reduce(function (prev, cur) {
return prev + cur;
}, 0);
// 3.利用forEach
function getSum(arr) {
let sum = 0;
arr.forEach((val) => {
sum += val;
});
return sum;
};
// 4.使用递归实现数组求和
function sum(arr) {
if (arr.length === 0) {
return 0;
}
return arr[0] + sum(arr.slice(1));
}