JavaScript 常用手写函数实现指南
本文汇总了 JavaScript 开发中常见的手写函数实现,涵盖原型方法如 bind、call、apply、push、flat 及去重,以及基础算法如深拷贝、阶乘、斐波那契数列、字符串字节长度计算、防抖节流和 Ajax 封装。通过解析核心逻辑与代码示例,帮助开发者深入理解语言机制与编程技巧。
前言
在 JavaScript 开发中,函数是代码执行的核心单元,绝大多数逻辑都依赖于函数调用。本文整理了一系列 JavaScript 中常用且高频的手写函数实现,旨在帮助开发者深入理解语言底层机制与常见编程模式。
JavaScript 常用函数实现汇总
JavaScript 常用函数实现汇总
实现 bind、call 和 apply 方法
一、实现 bind、call 和 apply 方法:这三个方法均定义在 Function 构造函数的原型上,主要作用是改变函数执行时的 this 指向,但不会立即执行原函数。
Function.prototype.myBind = function (target) {
if (typeof this !== 'function') {
throw Error('myBind is not a function')
}
var that = this
var args1 = [...arguments].slice(1)
var func = function () {
var args2 = [..arguments].slice(1)
return that.apply(target || window, args1.concat(args2)) }
return func
}
Function.prototype.myCall = function (context=window) {
if (typeof this !== 'function') {
throw Error('myBind is not a function')
}
context.fn = this
var args = [...arguments].slice(1)
var result = context.fn(..args)
delete context.fn
return result
}
Function.prototype.myApply = function (context=window) {
if (typeof this !== 'function') {
throw Error('myApply is not a function')
}
context.fn = this
var result
if (argument[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
实现引用数据类型的深拷贝
二、实现引用数据类型的深拷贝:该方法用于生成一个与原始对象完全独立的新对象,确保修改新对象不会影响原始数据。
function cloneDeep (target) {
function checkType(target) {
return Object.prototype.toString.call(target).slice(8, -1)
}
var result, checkedType = checkType(target)
if (checkedType === 'Array') {
result = []
} else if (checkedType === 'Object') {
result = {}
} else {
return target
}
//递归遍历对象或数组中的属性值或元素为原始值为止
for (var key in target) {
if ( checkType(target[key]) === 'Array' || checkType(target[key]) === 'Object') {
result[key] = cloneDeep(target[key])
} else {
result[key] = target[key]
}
}
return result
}
实现思路如下:
- 接收需要深拷贝的目标对象 target 作为输入,返回深拷贝后的新对象。
- 利用 Object.prototype.toString 精确判断 target 的数据类型。若 target 为对象或数组,则对其进行递归遍历;当遍历到的所有子元素均为基本数据类型时,递归终止,从而完成深层复制。
实现数组的 flat 方法
三、实现数组的 flat 方法:该方法用于将嵌套数组展开为一维数组。
Array.prototype.flat
实现 n 的阶乘计算
四、实现 n 的阶乘计算:阶乘是数学中常见的递归应用场景。
分析过程:以 3 的阶乘为例,其值为 3 * 2 * 1,即 n * (n-1) * ... * 1。可以看出计算过程具有规律性,且存在明确的终止条件(当 n 为 1 时停止)。因此,可以通过递归方式实现阶乘计算。
function factorial (num) {
if (num < 0) {
throw new Error('负数没有阶乘')
}
if (num === 1 || num === 0) {
return 1
}
return num * factorial(num-1)
}
factorial(3) //6
实现斐波那契数列
五、实现斐波那契数列:该数列是另一个经典的递归算法案例。
分析过程:与阶乘类似,斐波那契数列也具备明显的递归规律和终止条件,此处不再赘述具体推导过程。
function fibonacci (n) {
//此方法应使用尾递归法进行优化,这里不作优化,简单实现
if ( n <= 1 ) {return 1};
return fibonacci(n - 1) + fibonacci(n - 2);}
实现计算字符串字节长度的函数
六、实现计算字符串字节长度的函数:在处理国际化或多字节字符时,准确计算字节长度至关重要。
分析过程:英文字符通常占 1 个字节,中文字符占 2 个字节。通过 charCodeAt 方法获取当前字符的 Unicode 编码,若编码值大于 255,则判定为汉字,字节长度加 2;否则为英文字符,字节长度加 1。依此逻辑遍历字符串即可得到总字节长度。
function countBytesLength(str){
var length = 0
//首先遍历传入的字符串
for(var i = 0; i < str.length; i++) {
if (str[i].charCodeAt(i) > 255) {
length += 2
} else {
length++
}
}
return length
}
var str = 'DBCDouble陈'
countBytesLength(str) //11
实现 isNaN 函数
七、实现 isNaN 函数:用于判断传入值是否为非数字(Not a Number)。
分析过程:首先通过 Number 包装类对传入值进行隐式类型转换,得到转换后的结果。由于 NaN 与任何值(包括自身)比较均返回 false,因此不能直接比较。此处将转换后的结果转为字符串,若字符串为 'NaN',则返回 true,否则返回 false。
function isNaN(num) {
var ret = Number(num)
ret += ''
if (ret === 'NaN') {
return true
}
return false
}
isNaN('123abc') // true
实现数组的 push 方法
八、实现数组的 push 方法:该方法用于向数组末尾添加一个或多个元素。
分析过程:push 是定义在 Array.prototype 上的方法。其功能是将任意数量的参数追加到数组末尾,并返回数组的新长度。实现时需遍历参数列表,依次赋值给数组的新索引,最后返回 length 属性。
Array.prototype.push = function () {
for (var i = 0; i< arguments.length; i++) {
this[this.length] = arguments[i]
}
return this.length
}
实现增强版 typeof 函数
七、实现增强版 typeof 函数:原生 typeof 对 null 和数组等返回 'object',此处旨在实现能准确识别所有数据类型的类型判断方法。
分析过程:该方法挂载于 window 对象上。为了准确区分基本数据类型和复杂数据类型(如数组、正则等),需利用 Object.prototype.toString 方法获取内部 [[Class]] 属性,这是目前最可靠的类型判断方式。
window.typeof = function (value) {
return Object.prototype.toString.call(val).slice(8, -1)
}
实现数组去重方法
八、实现数组去重方法:用于移除数组中的重复元素。
分析过程:作为数组实例的方法,需在原型链上定义。通过遍历数组元素,利用对象键的唯一性(或 Set 结构)来过滤重复值,返回去重后的新数组。
Array.prototype.unique = function () {
//这里是利用对象键hash值的唯一性来去重
var obj = {}
var result = []
for (var i = 0; i < this.length; i++) {
if (!obj[this[i]]) {
obj[this[i]] = true
result.push(this[i])
}
}
return result
}
var arr = [1,2,2,3,3]
arr.unique() //[1,2,3]
Array.prototype.unique = function () {
//利用ES6的Array.prototype.includes
var result = []
for (var i = 0; i < this.length; i++) {
if (!result.includes(this[i])) {
result.push(this[i])
}
}
return result
}
Array.prototype.unique = function () {
//利用ES6的Set
var result = new Set(this) //生成一个类数组
return Array.from(result) //通过Array.from将类数组转换成真正的数组
}
Array.prototype.unique = function () {
//利用Array.prototype.filter返回符合条件的元素
//利用Array.prototype.indexOf返回数组中第一次出现当前元素的索引值
//该方法写法最为优雅,一行代码搞定,函数式编程
return this.filter((item, index) => this.indexOf(item) === index)
}
实现函数的防抖与节流
九、实现函数的防抖(debounce)与节流(throttle):用于控制函数执行频率,优化性能。
function debounce (fn, wait=300) {
var timer
return function () {
if (timer) {
clearTimeOut(timer)
}
timer = setTimeout({
fn.apply(this, arguments)
}, wait)
}
}
function throttle (fn, wait=300) {
var prev = +new Date()
return function () {
var now = +new Date()
if (prev - now > 300) {
fn.apply(this, arguments)
prev = now
}
}
}
封装 Ajax 请求
十、封装 Ajax 请求:基于 XMLHttpRequest 对象封装通用的异步请求方法。
function ajax (options) {
options = options || {}
options.url = options.url || ''
options.method = options.method.toUpperCase() || 'GET'
options.async = options.async || true
options.data = options.data || null
options.success = options.success || function () {}
var xhr = null
if (XMLHttpRequest) {
xhr = new XMLHttpRequest()
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP')
}
xhr.open(options.url, options.method, options.async)
var postData = []
for (var key in options.data) {
postData.push(key + '='+ options.data[key])
}
if (options.method === 'POST') {
xhr.open(options.method, options.url, options.async )
xhr.send(postData)
} else if (options.method === 'GET') {
xhr.open(options.mehtod, options.url + postData.join('&'), options.async)
xhr.send(null)
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
options.success(xhr.responseText)
}
}
}
实现 new 操作符
十一、实现 new 操作符:模拟 new 关键字的行为,包括创建对象、绑定原型、应用构造函数和返回对象。
function ajax (options) {
options = options || {}
options.url = options.url || ''
options.method = options.method.toUpperCase() || 'GET'
options.async = options.async || true
options.data = options.data || null
options.success = options.success || function () {}
var xhr = null
if (XMLHttpRequest) {
xhr = new XMLHttpRequest()
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP')
}
xhr.open(options.url, options.method, options.async)
var postData = []
for (var key in options.data) {
postData.push(key + '='+ options.data[key])
}
if (options.method === 'POST') {
xhr.open(options.method, options.url, options.async )
xhr.send(postData)
} else if (options.method === 'GET') {
xhr.open(options.mehtod, options.url + postData.join('&'), options.async)
xhr.send(null)
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
options.success(xhr.responseText)
}
}
}
实现六种常用继承方式
十二、实现六种常用继承方式:JavaScript 中实现面向对象继承有多种模式。
1、原型链继承:将子类型的原型对象设置为父类型的实例,从而继承父类的属性和方法。
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function () {
console.log(this.name)
}
function Student (height) {
this.height = height
}
Student.prototype = new Person()
var stu = new Student('175')
console.log(stu)
2、借用构造函数继承:在子类构造函数内部通过 call 或 apply 调用父类构造函数,实现属性继承,解决了原型引用类型共享的问题。
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function () {
console.log(this.name)
}
function Student (height, age, name) {
Person.call(this, age, name)
this.height = height
}
var stu = new Studeng(175, 'cs', 24)
console.log(stu)
3、组合继承:结合原型链和借用构造函数的优点。通过 call 调用父类构造函数继承属性并保留传参优势,同时将父类实例作为子类原型以继承方法,是 JavaScript 中最常用的继承方式。
function Person (name, age) {
this.name = name
this.age = age
}
Person
总结
以上涵盖了 JavaScript 核心手写函数的实现细节。掌握这些实现有助于深入理解语言特性,提升代码调试与优化能力。希望本文内容对您的学习与工作有所帮助。
JS合并多个数组的三种方法:concat、spread与flat
2026-11-05 17:39:45
JavaScript怎么准确判断数据类型:typeof、instanceof与Object.prototype.toString
2026-11-05 17:09:21
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
