0%

常见手写函数

一、柯里化函数

柯里化:

把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回 结果的新函数的技术

只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数

实现多数字加法的柯里化

加法函数:

1
2
3
var add0 = function(x,y,z){
return x + y + z
}

柯里化函数:

1
2
3
4
5
6
7
var add1 = function(x){
return function(y){
return function(z){
return x + y + z
}
}
}

箭头函数优化:

1
var add2 = x=>y=>z=>x+y+z

考虑会同时传递进去1个或2个参数

1
2
3
4
5
6
7
var add3 = (...args)=>{
if(args.length===3) return args.reduce((prev,curr)=>prev+curr)
return (...other)=>{
return add3(...args,...other)
}
}
// 未考虑传入参数大于3个时的边界情况

如果我们想得到可以获取不同个参数的柯里化函数呢,自动柯里化:

1
2
3
4
5
6
7
8
9
10
11
12
13
var fn = function(x,y,z,g){
return x+y+z+g
}
var curryMyFn= function(fn){
return curryFn = function(...args){
if(args.length>=fn.length) return fn(...args)
return (...newArgs)=>{
return curryFn(...args.concat(newArgs))
}
}
}
var bar = curryMyFn(fn)
console.log(bar(10,20)(40,50))

二、apply、call、bind的手写

this绑定

  • this绑定共4种,默认绑定、隐式绑定、显示绑定、new绑定
  • 实现显示绑定即是通过其余简单绑定方式实现,而默认绑定的this一般为windows,所以这里只能用隐式绑定实现

apply与call的手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 通过symbol实现apply
Function.prototype.myApply = function(thisArgs, args){
thisArgs = (thisArgs===null || thisArgs ===undefined)?window:Object(thisArgs)
let symbol = Symbol()
thisArgs[symbol] = this
let fn = thisArgs[symbol](...args)
delete thisArgs[symbol]
return fn
}
// 通过symbol实现call
Function.prototype.myCall = function(thisArgs, ...args){
thisArgs = (thisArgs===null || thisArgs ===undefined)?window:Object(thisArgs)
let symbol = Symbol()
thisArgs[symbol] = this
let fn = thisArgs[symbol](...args)
delete thisArgs[symbol]
return fn
}
// 不通过symbol、定义属性不可枚举
Function.prototype.myApply1 = function(thisArgs, args){
thisArgs = (thisArgs===null || thisArgs ===undefined)?window:Object(thisArgs)
Object.defineProperty(thisArgs, "temp",{
configurable:true,
Enumerable:false,
value:this
})
let fn = thisArgs["temp"](...args)
delete thisArgs["temp"]
return fn
}
// 不通过symbol且不定义属性不可枚举
Function.prototype.myApply2 = function(thisArgs, args){
thisArgs = (thisArgs===null || thisArgs ===undefined)?window:Object(thisArgs)
thisArgs.temp = this
let fn = thisArgs.temp(...args)
delete thisArgs.temp
return fn
}
  • 首先要排除thisArgs为null或undefined的情况,同时对数字或字符串包装为对象(Object(object)还是object)
  • 方法添加到Function的原型上,使得所有函数都可以直接调用
  • 使用symbol是因为Symbol()可以创建出一个独一无二的值,那么肯定不会覆盖掉原来对象中可能存在的值,同时Symbol属性不可以通过简单的属性获取得到,在后续的逻辑编写中不会出现错误
  • 如果不适用Symbol,则需要通过Object.defineProperty使其不可枚举Object.defineProperty

测试:

1
2
3
4
5
6
7
8
let bar = function(name,age){
console.log(this, Object.keys(this),name,age)
}

bar.myApply({method:"myApply-symbol"},["ouwenwu",18])
bar.myApply1({method:"myApply-没有symbol-不枚举"},["ouwenwu",18])
bar.myApply2({method:"myApply-没有symbol-枚举"},["ouwenwu",18])
bar.apply({method:"apply-js自带"},["ouwenwu",18])

image-20230326222135494

综上:直接使用symbol属性来完成手写

bind的手写实现

根据上面的经验,这里直接用symbol

bind与apply、call的区别主要是返回一个函数

1
2
3
4
5
6
7
Function.prototype.myBind = function(thisArgs,...args){
let symbol = Symbol()
thisArgs[symbol] = this
return function(...args){
return thisArgs[symbol](...args)
}
}

三、es5继承的实现

继承:子类继承父类的函数与属性

注:类具有显示原型、对象具有隐式原型

1.原型链继承

父类的实例化对象作为子类的显示原型——就可以通过原型链去访问得到父类的属性与方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./jquery.js"></script>
<style>
</style>
</head>
<body>
<script>
function Person(name,age){
this.name = name,
this.age = age
}
Person.prototype.eating = function(){
console.log("eating")
}
const person = new Person("oww", 23)
function Student(sno){
this.sno = sno
}
Student.prototype = person
Student.prototype.studying = function(){
console.log("studying")
}
const student = new Student("111")
student.eating()
student.studying()
</script>
</body>
</html>

image-20230405183113993

有一个很大的弊端:某些属性其实是保存在p对象上的:

  • 我们通过直接打印对象是看不到这个属性的
  • 这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题——其他子类修改会同时改变这边
  • 不能给Person传递参数(让每个stu有自己的属性),因为这个对象是一次性创建的(没办法定制化)——子类的name、age在student中写死了

2.借用构造函数继承

原型链继承子类中的属性是通过父类实例定义的,没有自己的属性,不能传入值更改,借用构造函数可以解决这个问题

  • 通过apply()和call()方法也可以在新创建的对象上执行父类构造函数