手撕万物Javascript篇
前言
本篇关于尽可能手撕一切可以实现的代码,先从js开始下手
防抖
code1
2
3
4
5
6
7
8
9
10
11
12function debounce(fn,timer){
var t = null;
return function(){
let firstClick = !t;
if(firstClick){
fn.apply(this,arguments);
}
t = setTimeout(() => {
t = null;
}, timer);
}
}
节流
code1
2
3
4
5
6
7
8
9
10function throttle(fn,delay){
var begin = 0;
return function(){
var cur = +new Date();
if(cur-begin>=delay){
fn.apply(this,arguments);
begin = cur;
}
}
}
浅拷贝
code1
2
3
4
5function extend(oldObj,newObj={}){
for (let item in oldObj) {
newObj[item] = oldObj[item];
}
}
深拷贝
api版本
code1
let newObj = JSON.parse(JSON.stringfy(oldObj));
缺陷:
- 无法实现对函数,正则等特殊对象的克隆
- 会抛弃对象的constructor,所有的构造函数会指向Object
- 对象有循环引用会报错
1.0版本
code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function deepClone(obj){
//判断是否是引用类型或者null 否则返回源对象进行浅拷贝
if(typeof obj !== 'object'||obj===null){
return obj;
}
//判断是否是数组,初始化地址。
let result;
if(obj instanceof Array){
result = [];
}else{
result = {};
}
//递归调用
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
2.0版本 解决循环引用
循环引用1
2
3var a = {b:122};
a.target = a;
deepClone(obj);//报错: RangeError: Maximum call stack size exceeded
先方法判断复杂类型
然后用map存储对象 如果已经存在则直接返回1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const judObj = ((target)=>(typeof target === 'object'||typeof target === 'function')&& target!==null);
const deepClone2 = ((target,map=new Map())=>{
if(map.get(target))return target;
if(judObj(target)){
map.set(target,true);
const res = Array.isArray(target)?[]:{};
for(let key in target){
if(target.hasOwnProperty(key)){
res[key] = deepClone2(target[key],map);
}
}
return res;
}else{
return target
}
})
//测试数据
const a = {val:2};
a.target = a;
let newA = deepClone2(a);
console.log(newA)//{ val: 2, target: { val: 2, target: [Circular] } }
but存在一个潜在的问题,就是map上的key和map构成了强引用关系,是很危险的。
科普强引用和弱引用的关系
被弱引用的对象在什么时候都可以被回收,但强引用的不行,上面的代码在程序结束之前都不会释放a的空间
所以需要用到weekMap构成弱引用,把Map改成weekMap即可
拷贝特殊对象
特殊对象
可遍历的特殊对象:1
2
3
4
5["object Map"]
["object Set"]
["object Array"]
["object Object"]
["object Arguments"]
不可遍历的特殊对象:1
2
3
4
5
6
7const boolTag = '[object Boolean]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
用最准确的那个方法判断
然后对于不能遍历的对象直接返回
对于能遍历的对象分类
如果是map的话 foreach 他的item和index赋值
如果是set foreach 他的item
其他的数组和对象 就直接用之前的方法遍历递归
注意要保留对象的原型
code1
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
39
40
41
42
43
44
45
46
47
48const judObj = ((target)=>(typeof target === 'object'||typeof target === 'function')&& target!==null);
const getObj = ((target)=>Object.prototype.toString.call(target));
const canTranverse = {
'[object Map]': true,
'[object Set]': true,
'[object Array]': true,
'[object Object]': true,
'[object Arguments]': true,
}
const deepClone3 = ((target,map=new Map)=>{
if(!judObj)return;
let type = getObj(target);
let cloneTarget;
if(!canTranverse[type]){
return;
}else{
let ctor = target.prototype;
cloneTarget = new ctor();
}
if(map.get(cloneTarget)){
return;
}else{
map.set(cloneTarget,true);
}
if(type === '[object Map]'){
target.forEach((item,index) => {
cloneTarget.set(deepClone3(item),deepClone3(index));
});
}
else if(type === '[object Set]'){
target.forEach(item=>{
cloneTarget.add(deepClone3(item));
})
}
else{
for (let key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone3(target[key],map);
}
}
}
return cloneTarget;
})
3.0 final
code1
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108const getType = obj => Object.prototype.toString.call(obj);
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;
const canTraverse = {
'[object Map]': true,
'[object Set]': true,
'[object Array]': true,
'[object Object]': true,
'[object Arguments]': true,
};
const mapTag = '[object Map]';
const setTag = '[object Set]';
const boolTag = '[object Boolean]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
const handleRegExp = (target) => {
const { source, flags } = target;
return new target.constructor(source, flags);
}
const handleFunc = (func) => {
// 箭头函数直接返回自身
if(!func.prototype) return func;
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
// 分别匹配 函数参数 和 函数体
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if(!body) return null;
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
}
const handleNotTraverse = (target, tag) => {
const Ctor = target.constructor;
switch(tag) {
case boolTag:
return new Object(Boolean.prototype.valueOf.call(target));
case numberTag:
return new Object(Number.prototype.valueOf.call(target));
case stringTag:
return new Object(String.prototype.valueOf.call(target));
case symbolTag:
return new Object(Symbol.prototype.valueOf.call(target));
case errorTag:
case dateTag:
return new Ctor(target);
case regexpTag:
return handleRegExp(target);
case funcTag:
return handleFunc(target);
default:
return new Ctor(target);
}
}
const deepClone = (target, map = new WeakMap()) => {
if(!isObject(target))
return target;
let type = getType(target);
let cloneTarget;
if(!canTraverse[type]) {
// 处理不能遍历的对象
return handleNotTraverse(target, type);
}else {
// 这波操作相当关键,可以保证对象的原型不丢失!
let ctor = target.constructor;
cloneTarget = new ctor();
}
if(map.get(target))
return target;
map.set(target, true);
if(type === mapTag) {
//处理Map
target.forEach((item, key) => {
cloneTarget.set(deepClone(key, map), deepClone(item, map));
})
}
if(type === setTag) {
//处理Set
target.forEach(item => {
cloneTarget.add(deepClone(item, map));
})
}
// 处理数组和对象
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], map);
}
}
return cloneTarget;
}
实现发布订阅EventEmitter
发布订阅者模式,一种对象间一对多的依赖关系,但一个对象的状态发生改变时,所依赖它的对象都将得到状态改变的通知。
主要的作用(优点):
广泛应用于异步编程中(替代了传递回调函数)
对象之间松散耦合的编写代码
缺点:
创建订阅者本身要消耗一定的时间和内存
多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护
实现的思路:
创建一个对象(缓存列表)
on方法用来把回调函数fn都加到缓存列表中
emit 根据key值去执行对应缓存列表中的函数
off方法可以根据key值取消订阅
发布订阅者模式和观察者模式的区别?
发布/订阅模式是观察者模式的一种变形,两者区别在于,发布/订阅模式在观察者模式的基础上,在目标和观察者之间增加一个调度中心。
观察者模式是由具体目标调度,比如当事件触发,Subject 就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。
发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。
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
32class Subject{
constructor(name){
this.state = 'happy';//状态
this.observers = [];//存储观察者
this.name = name;//被观察者名称
}
attach(o){
this.observers.push(o);
}
setState(newState){
this.state = newState;
this.observers.forEach(o=>o.update(this));
}
}
class Observer{
constructor(name){
this.name = name;//观察者名称
}
update(subject){
console.log('当前观察者'+this.name+'被通知了,当前被观察者'+subject.name+'状态是'+subject.state)
}
}
let student = new Subject('学生');
let parent = new Observer("父母");
let teacher = new Observer("老师");
// 被观察者存储观察者的前提,需要先接纳观察者
student.attach(parent);
student.attach(teacher);
student.setState("被欺负了");
instanceof
原理 找到实例对象的原型然后不断往上查找,如果和类实例的原型相同则为真,如果是null则为假1
2
3
4
5
6
7
8function myinstanceof(example,classFunc){
let proto = Object.getPrototypeOf(example);
while(true){
if(proto===null)return false;
if(proto===classFunc.prototype)return true;
proto = Object.getPrototypeOf(proto);
}
}
new
模拟new就要知道new操作符做了什么事情
- 将对象的隐式原型指向构造函数的原型prototype
- 执行构造函数,使用call/apply改变this指向
- 返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象。
1 | function mynew(fn,...args){ |
call
call不传入参数则默认绑定的window
新建函数将函数设置当前this
然后删除该函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Function.prototype.myCall = function(ctx = window,...args){
const fn = Symbol('fn');
ctx[fn] = this;
const res = ctx[fn](...args);
delete ctx[fn];
return res;
}
//用法:f.call(obj,arg1)
function f(a, b) {
console.log(a + b)
console.log(this.name)
}
let obj = {
name: 1
}
f.myCall(obj, 1, 2) //否则this指向window
apply
code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24Function.prototype.myApply = function(ctx=window,args){
try {
if(args&&!Array.isArray(args)){
throw Error('必须是数组');
}
const fn = Symbol('fn');
ctx[fn] = this;
const res = ctx[fn](...args);
delete ctx[fn];
return res;
} catch (error) {
console.log(error)
}
}
//用法:f.apply(obj,[arg1])
function f(a, b) {
console.log(a + b)
console.log(this.name)
}
let obj = {
name: 1
}
f.myApply(obj, [1, 2]) //否则this指向window
f.myApply(obj, 1,2) //否则this指向window
bind
code1
2
3
4
5
6
7
8
9
10
11
12
13
14Function.prototype.mybind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('err');
}
var self = this;
var args = [...arguments].slice(1);
return function F() {
//判断是否是new 是的话变成指向function 否则指向context
var _this = this instanceof self ? this : context;
//改变f的原型链
F.prototype = self.prototype;
return self.apply(_this, args.concat(...arguments));
}
}
Object.create
1 | function create(proto){ |
类的继承
Promise.resolve
- 值是promise对象则直接返回
- 返回一个new promise且如果他是then调用,则返回then方法
- 其他情况,返回成功状态的promise
code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 1. promise return
// => new promise
// 2. param.then === 'fun'
// => param.then(res,rej)
// 3. return resolve(param)
Promise.resolve = (param => {
if (param instanceof Promise) return param;
return new Promise((resolve, reject) => {
if (param && param.then && typeof param.then === 'function') {
return param.then(resolve, reject);
} else {
return resolve(param)
}
})
})
Promise.reject
reject传入的参数会作为一个reason原封不动的往下传1
2
3
4
5Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
Promise.prototype.finally
不管状态如何都会执行并且往下传值.1
2
3
4
5
6
7
8
9
10
11Promise.prototype.finally = function(cb){
this.then(value=>{
return Promise.resolve((cb()).then(()=>{
return value;
}))
},error=>{
return Promise.resolve((cb()).then(()=>{
throw error;
}))
})
}
Promise.all
code如下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
27Promise.myall = function(iterable){
//传入的是可迭代对象
let index = 0; //这里不一定是数组,不一定有length,所以要变量计算.
let elementCount = 0; //解决的promise数量
let anErrorOccurred = false;//判断是否错误
for (const promise of iterable) {
const curIndex = index; //封闭index的作用域
promise.then((value)=>{
if(anErrorOccurred)return;
result[curIndex] = value;
elementCount++;
if(elementCount === result.length){
resolve(result)//如果全部任务都成功,返回数组
}
},(err)=>{
if(anErrorOccurred)return;
anErrorOccurred=true;
reject(err);
})
index++;
}
if(index === 0){
resolve([]);//长度是0 返回空数组
return;
}
const result = new Array(index);//要在最后写result 因为index是在循环后才计算出来.
}
Promise.race
race方法只要有一个率先改变了状态,后面就直接resolve1
2
3
4
5
6
7
8
9
10
11
12
13
14Promise.myrace = function (iterable) {
let settlementOccurred = false;
for (const promise of iterable) {
promise.then((value) => {
if (settlementOccurred) return;
settlementOccurred = true;
resolve(value);//直接resolve
}, (err) => {
if (settlementOccurred) return;
settlementOccurred = true;
reject(err);//直接reject
})
}
}
Promise.allSettled
执行完后不会失败,按顺序返回每个promise状态1
2
3
4
5
6
7
8
9
10
11const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// 返回结果:
// [
// { status: 'fulfilled', value: 2 },
// { status: 'rejected', reason: -1 }
// ]
code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25Promise.allSettled = function (promiseArgs) {
if (!promiseArgs.length) return Promise.resolve([]);
// 包装不是promise的项
const promises = promiseArgs.map(p => p instanceof Promise ? p : Promise.resolve(p));
return new Promise((resolve, reject) => {
let res = [];
let unSettledCount = promises.length;
promises.forEach((p, index) => {
p.then((reason) => {
res[index] = {
status: "resolve",
reason
}
}, (reason) => {
res[index] = {
status: "reject",
reason
}
}).finally(() => {
--unSettledCount;
if (!unSettledCount) return resolve(res);
})
});
})
}
Promise.retry超时请求
简易版1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24Promise.retrySimple = function (fn, maxRetry, timeout) {
if (typeof fn !== 'function') {
throw new TypeError('Expected a function');
}
maxRetry = maxRetry || 3;
timeout = timeout || 1000;
let retryCount = 0;
return new Promise((resolve, reject) => {
const run = () => {
fn().then((value) => {
resolve(value);
}, (err) => {
if (retryCount >= maxRetry) {
reject(err);
return;
}
setTimeout(run,timeout);
retryCount++;
})
}
run();
})
}
有缓存版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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57// 完整版 - 有缓存
Promise.retry = function (fn, options) {
// 参数检查
if (typeof fn !== 'function') {
throw new TypeError('Expected a function');
}
options = options || {};
// 默认参数
options = Object.assign({
maxRetry: 3, // 默认重试次数
retryDelay: 1000, // 默认重试时间间隔
cache: false, // 是否缓存结果
cacheKey: "", // 缓存 key
cacheExpire: 0, // 缓存过期时间,单位:毫秒
cacheMax: 0 // 缓存最大值,超过后清空缓存
}, options);
let retryCount = 0; // 已重试次数
return new Promise((resolve, reject) => {
// 内部函数,进行一次尝试
const run = () => {
fn().then(
(value) => {
// 成功收到响应,如果需要缓存,则缓存结果,同时设置缓存过期时间
if (options.cache) {
localStorage.setItem(options.cacheKey, JSON.stringify({
value,
expire: Date.now() + options.cacheExpire
}));
}
// 封装的 promise 解决
resolve(value);
},
(err) => {
// 超过了最大重试次数,拒绝
if (retryCount >= options.maxRetry) {
reject(err);
return;
}
// 没有超过重试次数,如果有缓存,则读取缓存
if (options.cache) {
const cache = localStorage.getItem(options.cacheKey);
if (cache) {
const cacheObj = JSON.parse(cache);
if (cacheObj.expire > Date.now()) {
resolve(cacheObj.value);
return;
}
}
}
// 重试
setTimeout(run, options.retryDelay);
retryCount++;
});
};
run();
});
};
解析URL Params对象
实现JS函数柯里化
柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,返回原函数。
思路如下:
- 利用length属性获取函数的形参个数
- 手动指定需要的形参个数
ES5版本(涉及到arguments的es5转换法)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function mycurry(fn,args){
const len = fn.length;
var args = args || [];
return function (){
//arguments的逐次分割
var newArgs = args.concat(Array.prototype.slice.call(arguments));
//不满足携带newArgs递归
if(newArgs.length<len){
return mycurry.call(this,fn,newArgs);
}else{
//满足直接返回
return fn.apply(this,newArgs);
}
}
}
function multiFn(a,b,c){
return a*b*c
}
var fn = mycurry(multiFn);
console.log(fn(2)(3)(4))
console.log(fn(2,3,4))
console.log(fn(2)(3,4))
console.log(fn(2,3)(4))
ES6版本
es6可以直接用拓展运算符所以会写起来简单一点1
数组扁平化
要求:1
2
3let arr = [1,[2,[3,4],5],6]
=>
[1,2,3,4,5,6]
方法1:api版本1
2let arr = [1,[2,[3,4],5],6];
console.log(arr.flat(Infinity))
方法2:reduce1
2
3
4
5
6
7let arr = [1,[2,[3,4],5],6];
function flatten(arr){
return arr.reduce((pre,cur)=>{
return pre.concat(cur instanceof Array?flatten(cur):cur);
},[])
}
console.log(flatten(arr))
方法3:转字符串1
2
3
4
5let arr = [1,[2,[3,4],5],6];
function flatten(arr){
return arr = arr.join(",").split(",").map(item=>~~item);
}
console.log(flatten(arr))
解析URL
要求:可以传入数组或者对象,编写一个myURLSearchParams类,包含get,set,has,append1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16"foo=1&bar=2"
{
foo: "1",
bar: "2"
}
console.log(searchParams.get("foo")) //1
console.log(searchParams2.get("foo")) //1
searchParams.set("foo","33");
searchParams2.set("foo","33");
console.log(searchParams) //{ url: 'foo=33&bar=2' }
console.log(searchParams2) //{ url: { foo: '33', bar: '2' } }
console.log(searchParams.has("foo")) //true
console.log(searchParams2.has("foo")) //true
searchParams.append("foo","3casc3"); //{ url: 'foo=33&bar=2&foo=3casc3' }
searchParams2.append("foo","3casc3"); // url: { foo: '3casc3', bar: '2' } }
code1
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53class myURLSearchParams {
constructor(url) {
this.url = url;
}
get(target) {
if (this.url instanceof Object) {
return this.url[target]
} else {
let arr = this.url.split('&');
for (let item of arr) {
let [key, val] = item.split('=');
if (key === target) {
return val;
}
}
}
}
append(key,value){
if(this.url instanceof Object){
this.url[key] = value;
}else{
this.url+=("&"+key+"="+value);
}
}
set(key,value){
if(this.url instanceof Object){
this.url[key] = value;
}else{
let arr = this.url.split('&');
for (let item of arr) {
let [keys, val] = item.split('=');
if (keys === key) {
let rep = `${key}=${val}`
let rep2 = `${key}=${value}`
this.url = this.url.replace(rep,rep2)
break;
}
}
}
}
has(target){
if(this.url instanceof Object){
return this.url[target]?true:false;
}else{
return this.url.match(target)?true:false;
}
}
}
let searchParams = new myURLSearchParams("foo=1&bar=2");
let searchParams2 = new myURLSearchParams({
foo: "1",
bar: "2"
});
删除指定的URL中的某个参数
需求:删除URL中的某个参数并且确保没有重复的出现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
26let query = 'https://www.baidu.com/s?ie=utf-8&f=8&key2=99&key2=99';
function myQueryDelete(query,target){
let queryArr = query.split("?")[1].split("&");
console.log(queryArr)
let index = 0;
let len = queryArr.length-1;
for (let item of queryArr) {
let [key,value] = item.split("=");
if(key === target){
if(index===len){
query = query.replace(`${key}=${value}`,'')
}else{
query = query.replace(`${key}=${value}&`,'')
}
}
index++;
}
while(query[query.length-1]==='&'){
query = query.slice(0,-1);
}
console.log(query)
}
myQueryDelete(query,"key2")
//https://www.baidu.com/s?ie=utf-8&f=8
js生成二叉树
需求: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
26let testData = [{
id: 5,
parent: 4
},
{
id: 2,
parent: 0
},
{
id: 3,
parent: 1
},
{
id: 1,
parent: 0
},
{
id: 4,
parent: 1
},
{
id: 0,
parent: -1
},
]
转二叉结构
code如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const toTree = (arr => {
let map = {};
let res = [];
for (const item of arr) {
map[item.id] = {
...item,
children: []
};
}
for (const item of arr) {
let id = item.id;
let pid = item.parent;
let treeItem = map[id];
if (pid === -1) {
res.push(treeItem);
} else {
map[pid].children.push(treeItem)
}
}
console.log(map)
})
toTree(testData)
转化为驼峰命名
利用正则1
2
3
4
5
6
7
8
9
10
11var s1 = "get-element-by-id"
const fn = (word)=>{
// 匹配-\w,并第一个字母替换成大写字母
// 利用到了replace的第二个参数,传输函数处理
return word.replace(/-\w/g,(x)=>{
return x.slice(1).toUpperCase();
})
}
console.log(
fn(s1)
)