一、实现一个 new 操作符
new 的原理是什么?通过 new的方式创建对象和通过字面量创建有什么区别?
new原理
在调用 new
的过程中会发生四件事情
- 创建一个空对象,构造函数中的
this
指向这个空对象 - 新对象被链接到原型
- 执行构造函数方法,属性和方法被添加到
this
引用的对象中 - 如果构造函数中没有返回新对象,那么返回
this
,即创建这个新对象,否则,返回构造函数中返回的对象
实现一个 new 并测试
//实现2
function myNew(func, ...args) {
const obj = {}; // 新建一个空对象
const result = func.call(obj, ...args); // 执行构造函数
obj.__proto__ = func.prototype; // 设置原型链
// 注意如果原构造函数有Object类型的返回值,包括Functoin, Array, Date, RegExg, Error
// 那么应该返回这个返回值
const isObject = typeof result === 'object' && result !== null;
const isFunction = typeof result === 'function';
if(isObject || isFunction) {
return result;
}
// 原构造函数没有Object类型的返回值,返回我们的新对象
return obj;
}
//test
function Dog(age) {
this.age = age;
}
Dog.prototype.say = function () {
console.log("汪汪汪");
}
const myDog = myNew(Dog, 2);
console.log(myDog.age); // 2
myDog.say(); // 汪汪汪
二、实现一个 call、apply
1、call特性(非严格模式)
- 如果
obj.call(null)
,那么this
应该指向window
- 如果
obj1.call(obj2)
,那么谁调用它,this
指向谁(这里就是obj2
了)call
可以传入多个参数,所以可以利用arguments
这个字段来获取所有参数。将arguments
转换数组后,获取除第一个参数外的其他参数- 设置一个变量,用完删掉它
实现一个call并测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>手写 call并测试</title>
</head>
<body>
<button class="btn">123</button>
<script>
(function () {
//手写实现call
Function.prototype.myCall = function (context) {
// 不传context的话,默认上下文为window
const newContext = context || window;
// 设置fn为调用 myCall 的方法
newContext.fn = this;
// 获取剩余参数
const otherArg = Array.from(arguments).slice(1);
// 调用这个方法,将剩余参数传递进去
const result = newContext.fn(otherArg);
// 删除这个变量
delete newContext;
return result;
};
//test
const debounce = function (fn) {
let timer = null;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
fn.myCall(this, arguments);
}, 1000);
}
}
let time = 0;
const getNumber = function () {
console.log(++time);
}
const btn = document.querySelector('.btn');
btn.addEventListener('click', debounce(getNumber));
})()
</script>
</body>
</html>
//test
function getName() {
console.log(this.name)
}
let obj = {
a: 1,
name: 'xiuzhu'
}
getName.myCall(obj) //xiuzhu
2、apply特性(非严格模式)
apply
:和call
基本上一致,唯一区别在于传参方式
call后面传递的参数是以逗号的形式分开的,
apply
传递的参数是数组形式apply
是以A开头,所以这里可以跟Array
有关联,即参数为数组[arg1, arg2...]
,虽然写的是一个数组,但是也相当于一个一个传递
fn.call(obj, 1, 2);
fn.apply(obj, [1, 2]);
实现一个apply并测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>手写apply</title>
</head>
<body>
<button class="btn">123</button>
<script>
(function () {
//手写实现apply
Function.prototype.myApply = function (context, arr) {
// 不传context的话,默认上下文为window
const newContext = context || window;
// 设置fn为调用 myApply 的方法
newContext.fn = this;
// 调用方法将参数传入
if (!arr) {
result = newContext.fn();
} else {
result = newContext.fn(arr);
}
// 删除这个变量
delete newContext;
return result;
};
const debounce = function (fn, number) {
let timer = null;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
fn.myApply(this, number);
}, 1000);
}
}
const getNumber = function (number) {
console.log(number);
}
let number = [1, 2, 3, 4, 5];
const btn = document.querySelector('.btn');
btn.addEventListener('click', debounce(getNumber, number));
})()
</script>
</body>
</html>
三、实现一个 bind
bind
会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
bind
:语法和call
一模一样,区别在于立即执行还是等待执行,bind
不兼容IE6~8
改变 this 作用域会返回一个新的函数,这个函数不会马上执行
fn.call(obj, 1, 2); // 改变fn中的this,并且把fn立即执行
fn.bind(obj, 1, 2); // 改变fn中的this,fn并不执行
call和bind的区别
call
将fn
的this
改变为obj
了,但是绑定的时候立即执行,当触发点击事件的时候执行的是fn
的返回值undefined
document.onclick = fn.call(obj);
bind
会把fn
中的this
预处理为obj
,此时fn
没有执行,当点击的时候才会把fn
执行
document.onclick = fn.bind(obj);
实现一个bind并测试
Function.prototype.myBind = function(context = globalThis) {
// 设置 fn 为调用 myCall 的方法
const fn = this;
// 获取该方法剩余参数
const otherArg = [...arguments].slice(1);
// 设置返回的一个新方法
const result = function() {
// 获取返回方法体的参数
const resultArg = [...arguments];
// 如果是通过 new 调用的,绑定 this 为实例对象
if (this instanceof result) {
fn.apply(this, otherArg.concat(resultArg));
} else { // 否则普通函数形式绑定 context
fn.apply(context, otherArg.concat(resultArg));
}
}
// 绑定原型链
result.prototype = Object.create(fn.prototype);
// 返回结果
return result;
};
//test
function foo(something) {
console.log(this.a, something)
return this.a + something
}
obj = {
a: 2
}
let bar = foo.myBind(obj)
bar(3) //2 3
四、实现一个继承
1、组合继承
组合继承是最常用的继承方式
function Parent(value) {
this.val = value
}
//原型式继承
Parent.prototype.getValue = function() {
console.log(this.val)
}
//构造函数继承
function Child(value) {
Parent.call(this, value)
}
//原型继承
Child.prototype = new Parent()
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
- 继承方式优点:实现了函数复用,且能在构造函数中传参,不会与父类引用属性共享,可以复用父类的函数
- 缺点:会调用两次超类型的构造函数,一次在创建子类型原型的时候,另一次在子类型构造函数内部。就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费
2、寄生组合继承
引用类型最理想的继承范式
前面的组合继承有个缺点:每次创建实例时都会调用两次超类方法,一次是通过
new
设置原型的时候,另一次是用call
执行的时候
function Parent(value) {
this.val = value
}
//原型式继承
Parent.prototype.getValue = function() {
console.log(this.val)
}
//构造函数继承
function Child(value) {
Parent.call(this, value)
}
//寄生继承
//Object.create()是ES5规范化的原型式继承:一个是用作新对象原型的对象和
//(可选的)一个新对象定义额外属性的对象
// 如下是补充因为重写Parent原型而失去默认的constructor属性,最后将新创建的对象赋值给子类型的原型
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true
}
})
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
- 思路:不需要为了指定子类型的原型而调用超类型的构造函数(我理解为就是不需要显示的new操作),通过上面的寄生式继承方式来继承超类型的原型即可。
五、实现一个 instanceof
判断数据类型typeof、instanceof、constructor和Object.prototype.toString.call()
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。简单来说就是 instanceof
是用来判断 A
是否为 B
的实例。
根据上述描述,实现一个instanceof
function myInstanceof(left, right) {
let prototype = right.prototype
left = left.__proto__
while (true) {
if (left === null || left === undefined)
return false
if (prototype === left)
return true
left = left.__proto__
}
}
//测试
var arr = [1, 2, 3];
console.log(myInstanceof(arr, Array)) // true
六、手写(Debouncing)和节流(Throttling)
1、防抖(Debouncing)实现
// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 1000) => {
// 缓存一个定时器id
let timer = 0
// 这里返回的函数是每次用户实际调用的防抖函数
// 如果已经设定过定时器了就清空上一次的定时器
// 开始一个新的定时器,延迟执行用户传入的方法
return function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
典型例子:限制鼠标连击触发。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>手写 call</title>
</head>
<body>
<button class="btn">123</button>
<script>
(function () {
const debounce = (func, wait = 1000) => {
// 缓存一个定时器id
let timer = 0
// 这里返回的函数是每次用户实际调用的防抖函数
// 如果已经设定过定时器了就清空上一次的定时器
// 开始一个新的定时器,延迟执行用户传入的方法
return function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
let time = 0;
const getNumber = function () {
console.log(++time);
}
const btn = document.querySelector('.btn');
btn.addEventListener('click', debounce(getNumber));
})()
</script>
</body>
</html>
2、节流(Throttling)实现
每隔一段时间,只执行一次函数。
// func是用户传入需要节流的函数
// wait是等待时间
const throttle = (func, wait = 50) => {
// 上一次执行该函数的时间
let lastTime = 0
return function(...args) {
// 当前时间
let now = +new Date()
// 将当前时间和上一次执行函数时间对比
// 如果差值大于设置的等待时间就执行函数
if (now - lastTime > wait) {
lastTime = now
func.apply(this, args)
}
}
}
//test:1s输出一次1
setInterval(
throttle(() => {
console.log(1)
}, 1000),
1
)
七、手写深浅拷贝
根据拷贝的层级不同可以分为浅拷贝和深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝
1、实现浅拷贝
//浅拷贝
function shallowClone(target) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
}
2、实现深拷贝
深拷贝的问题其实可以分解成两个问题,浅拷贝+递归
简版
//深拷贝
function deepClone(target) {
if (typeof target === 'object') {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = deepClone(target[key]);
}
return cloneTarget;
} else {
return target;
}
}
完整版:解决参数检验不严谨、没有考虑数组、循环引用的问题
//代码
function isObject(x) {
return Object.prototype.toString.call(x) === '[object Object]'
}
function deepClone(target, map = new Map()) {
// 使用isObject对参数做检验,判断是否对象
if (isObject(target)) {
// 考虑数组
let cloneTarget = Array.isArray(target) ? [] : {};
//使用map解决循环引用问题
if (map.has(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = deepClone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
}
//测试用例
const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8]
};
target.target = target
var cloneTarget = deepClone(target)
console.log(cloneTarget)
输出:target属性,变为了一个Circular类型,即循环引用的意思。
八、实现一个JSON.stringify和JSON.parse
JSON.parse
/JSON.stringify
还是有很多局限性,大致如下:
- 会忽略
undefined
- 会忽略
Symbol
- 无法序列化
function
,也会忽略 - 无法解决循环引用,会报错
- 深层对象转换爆栈
1、实现JSON.stringify
JSON.stringify(value[, replacer [, space]]):
Boolean
|Number
|String
类型会自动转换成对应的原始值。undefined
、任意函数以及symbol
,会被忽略(出现在非数组对象的属性值中时),或者被转换成null
(出现在数组中时)。- 不可枚举的属性会被忽略
- 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。
实现JSON.stringify
可以序列化引用类型和基本类型。
// 数据类型判断
function getType(attr) {
let type = Object.prototype.toString.call(attr);
let newType = type.substr(8, type.length - 9);
return newType;
}
// 转换函数
function StringIfy(obj) {
// 如果是非object类型 or null的类型直接返回 原值的String
if (typeof obj !== "object" || getType(obj) === null) {
return String(obj);
}
// 声明一个数组
let json = [];
// 判断当前传入参数是对象还是数组
let arr = obj ? getType(obj) === "Array" : false;
// 循环对象属性
for (let key in obj) {
// 判断属性是否在对象本身上
if (obj.hasOwnProperty(key)) {
// console.log(key, item);
// 获取属性并且判断属性值类型
let item = obj[key];
if (item === obj) {
console.error(new TypeError("Converting circular structure to JSON"));
return false;
}
if (/Symbol|Function|Undefined/.test(getType(item))) {
delete obj[key];
continue;
}
// 如果为object类型递归调用
if (getType(item) === "Object") {
// consoarrle.log(item)
item = StringIfy(item);
}
let IsQueto =
getType(item) === "Number" ||
getType(item) === "Boolean" ||
getType(item) === "Null"
? ""
: '"';
// 拼接数组字段
json.push((arr ? IsQueto : '"' + key + '":"') + String(item) + IsQueto);
}
}
// 转换数组字段为字符串
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
//测试
let target = {
name: "name",
age: undefined,
func: function() {},
sym: Symbol("setter")
};
console.log(StringIfy(target))
let json = JSON.stringify(target)
console.log(json) //"{"name":"name"}"
1、实现JSON.parse
将json
字符串转换为对象
//方法一:直接用eval
function jsonParse(opt) {
return eval('(' + opt + ')');
}
jsonParse(jsonStringify({x : 5}))
// Object { x: 5}
jsonParse(jsonStringify([1, "false", false]))
// [1, "false", falsr]
jsonParse(jsonStringify({b: undefined}))
// Object { b: "undefined"}
//方法二:Function
var jsonStr = '{ "age": 20, "name": "jack" }'
var json = (new Function('return ' + jsonStr))();
九、实现一个 JS 函数柯里化
什么是柯里化?
用通俗易懂的话说:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。
函数柯里化的实现
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function () {
newArgs = args.concat(Array.prototype.slice.call(arguments));
//如果参数个数小于最初的fn.length,则递归调用,继续收集参数
if (newArgs.length < length) {
return curry.call(this, fn, newArgs);
}
//参数收集完毕,则执行fn
return fn.apply(this, newArgs);
}
}
//test
function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
console.log(multi(2)(3)(4)) //都输出24
console.log(multi(2, 3, 4))
console.log(multi(2)(3, 4))
console.log(multi(2, 3)(4))
十、请实现 Promise 的这个世界
过一遍Promise/A+规范:
- 三种状态
pending
|fulfilled
(resolved) |rejected
- 当处于
pending
状态的时候,可以转移到fulfilled
(resolved)或者rejected
状态 - 当处于
fulfilled
(resolved)状态或者rejected
状态的时候,就不可变。
必须有一个then异步执行方法,then接受两个参数且必须返回一个promise:
// onFulfilled 用来接收promise成功的值
// onRejected 用来接收promise失败的原因
promise1=promise.then(onFulfilled, onRejected);
忆往昔:Promise用法
var promise = new Promise((resolve, reject) => {
if (操作成功) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function (value) {
// success
}, function (value) {
// failure
})
1、实现一个Promise
function myPromise(constructor) {
let self = this;
self.status = "pending" //定义状态改变前的初始状态
self.value = undefined;//定义状态为resolved的时候的状态
self.reason = undefined;//定义状态为rejected的时候的状态
function resolve(value) {
//两个==="pending",保证了状态的改变是不可逆的
if (self.status === "pending") {
self.value = value;
self.status = "resolved";
}
}
function reject(reason) {
//两个==="pending",保证了状态的改变是不可逆的
if (self.status === "pending") {
self.reason = reason;
self.status = "rejected";
}
}
//捕获构造异常
try {
constructor(resolve, reject);
} catch (e) {
reject(e);
}
}
//在myPromise的原型上定义链式调用的then方法
myPromise.prototype.then = function (onFullfilled, onRejected) {
let self = this;
switch (self.status) {
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
var p = new myPromise(function (resolve, reject) { resolve(123) });
p.then(function (x) { console.log(x) })
//输出123
2、手撕 Promise.all
Promise.all
接收一个 promise
对象的数组作为参数,当这个数组里的所有 promise
对象全部变为resolve
或 有 reject
状态出现的时候,它才会去调用 .then
方法,它们是并发执行的。
实现方法
Promise.myAll = function (promises) {
return new Promise(function (resolve, reject) {
var resolvedCounter = 0
var promiseNum = promises.length
var resolvedValues = new Array(promiseNum)
for (var i = 0; i < promiseNum; i++) {
(function (i) {
Promise.resolve(promises[i]).then(function (value) {
resolvedCounter++
resolvedValues[i] = value
if (resolvedCounter == promiseNum) {
return resolve(resolvedValues)
}
}, function (reason) {
return reject(reason)
})
})(i)
}
})
}
// test
var p1 = Promise.resolve(1),
p2 = Promise.reject(2),
p3 = Promise.resolve(3);
Promise.myAll([p1, p2, p3]).then(function (results) {
//then方法不会被执行
// 成功输出则输出数组
console.log(results);
}).catch(function (e) {
//catch方法将会被执行,输出结果为:2
console.log(e);
});
es6
Promise.prototype.myAll = function (promises) {
return new Promise((resolve, reject) => {
let resolvedCounter = 0
let promisesNum = promises.length
let resolvedValue = new Array(promisesNum)
promises.forEach((promise, i) => {
promise.then(
value => {
resolvedCounter++
resolvedValue[i] = value
if (resolvedCounter === promisesNum) {
return resolve(resolvedValue)
}
},
reason => {
return reject(reason)
}
)
})
})
}
Promise.all
总结
- 接收一个
Promise
实例的数组或具有Iterator
接口的对象, - 如果元素不是
Promise
对象,则使用Promise.resolve
转成Promise
对象 - 如果全部成功,状态变为
resolved
,返回值将组成一个数组传给回调 - 只要有一个失败,状态就变为
rejected
,返回值将直接传递给回调
all()
的返回值也是新的Promise
对象
3、手撕Promise.allSettled
我们期望传入的这组 promise
无论执行失败或成功,都能获取每个 promise
的执行结果,为此,ES2020 引入了 Promise.allSettled()
Promise.allSettled()
可以获取数组中每个 promise 的结果,无论成功或失败
Promise.myAllSettled = function (promises) {
if (promises.length === 0) return Promise.resolve([])
const _promises = promises.map(
item => item instanceof Promise ? item : Promise.resolve(item)
)
return new Promise((resolve, reject) => {
const result = []
let unSettledPromiseCount = _promises.length
_promises.forEach((promise, index) => {
promise.then((value) => {
result[index] = {
status: 'fulfilled',
value
}
unSettledPromiseCount -= 1
// resolve after all are settled
if (unSettledPromiseCount === 0) {
resolve(result)
}
}, (reason) => {
result[index] = {
status: 'rejected',
reason
}
unSettledPromiseCount -= 1
// resolve after all are settled
if (unSettledPromiseCount === 0) {
resolve(result)
}
})
})
})
}
// 测试
var p1 = Promise.resolve(1),
p2 = Promise.reject('错误'),
p3 = Promise.resolve(3)
Promise.myAllSettled([p1, p2, p3]).then(function (results) {
console.log(results)
}).catch(function (e) {
console.log(e)
})
//输出
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: '错误' },
{ status: 'fulfilled', value: 3 }
]
4、手撕Promise.race
/**
* @func Promise 函数对象的 race()
* @param promises 请求数组
* @returns 返回一个 Promise,其结果由第一个完成的 Promise 决定
*/
Promise.myRace = function (promises) {
// 返回一个新的 Promise
return new Promise((resolve, reject) => {
// 遍历 promises 取出每个 Promsie 的结果
promises.forEach(p => {
Promise.resolve(p).then(
value => {
// 一旦有一个成功了, return 的 Promise 就成功
resolve(value);
},
reason => {
// 一旦有一个失败了, return 的 Promise 就失败
reject(reason);
}
)
})
})
}
5、手撕Promise.finally
用于指定不管Promise对象最后状态如何都会执行的操作,接收一个回调函数作为参数。
// 实现Promise.finally
Promise.prototype.finally = function (callback) {
let P = this.constructor
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
)
}
6、Promise
串行
Promise串行习题
定义 type Task = () => Promise (即 Task 是一个 类型,是一个返回值是 Promise 的函数类型)
假设有一个数组 tasks: Task[](每一项都是一个 Task 类型的数组)
实现一个方法 function execute(tasks: Task[]): Promise,该方法将 tasks 内的任务 依次 执行,并返回一个结果为数组的 Promise ,该数组包含任务执行结果(以执行顺序排序)
要求: 忽略异常任务,并在结果数组中用 null 占位
限制: 不添加任何依赖,仅使用 Promise,不使用 Generator 或 async
思路大致如下图
先做一个Promise实例,然后把每个Task循环的放置到上一个promise的then回调里。
需要注意的几点:
- 无论每个Task是成功还是失败,它都不能阻断下一个Task的执行
- 最后的then需要把每个Task的执行结果"决议"出去
对策:
- 每一个Task外层包装一层Promise,捕获Task的reject状态
- 可以利用一个中间变量,缓存所有Task的输出结果,然后在最后一个Promise的then里把中间变量“决议”出去
function execute(tasks) {
return tasks.reduce(
(previousPromise, currentPromise) => previousPromise.then((resultList) => {
return new Promise(resolve => {
currentPromise().then(result => {
resolve(resultList.concat(result))
}).catch(() => {
resolve(resultList.concat(null))
})
})
}),
Promise.resolve([])
)
}
//测试用例
let Task = (result, isSuccess = true) => {
return () => new Promise((resolve, reject) => {
setTimeout(() => {
if (isSuccess) {
console.log(`success: ${result}`);
resolve(result);
} else {
console.log(`error: ${result}`);
reject(result);
}
}, 1000);
});
}
execute([
Task('A'),
Task('B'),
Task('X', false),
Task('C'),
]).then(resultList => {
// 这里期望打印 ["A", "B", null, "C"]
console.log(resultList)
})
// aysnc/await版本
// aycns/await要与迭代器进行配合,所以应该利用for of来配合使用。
const execute = async (tasks = []) => {
const resultList = [];
for (task of tasks) {
try {
resultList.push(await task());
} catch (e) {
resultList.push(null);
}
}
return resultList;
}
十一、实现indexOf
String
和Array
两种数据类型都可以调用该API;- 该接口有两个参数:
str.indexOf(searchVal, fromIndex)
;其中,searchVal
支持多字符,且对大小写敏感; - 匹配成功,返回首次匹配子内容的索引;
- 匹配失败,返回
-1
;
//基于字符串
function sIndexOf(str, searchStr, fromIndex) {
let strLen = str.length
let searchLen = searchStr.length
if (searchLen === 0) {
if (fromIndex > strLen) return strLen
else if (fromIndex > 0 && fromIndex < strLen) return fromIndex
}
if (fromIndex < 0) fromIndex = 0
// if (fromIndex >= strLen) return -1
for (let i = fromIndex; i < strLen - searchLen; i++) {
if (str.slice(i, i + searchLen) === searchStr) return i
}
return -1
}
//基于数组
function aIndexOf(arr, searchVal, fromIndex = 0) {
let arrLen = arr.length
if (fromIndex < 0) fromIndex += arrLen
// if (fromIndex >= arrLen) return -1
for (let i = fromIndex; i < arrLen; i++) {
if (arr[i] === searchVal) return i
}
return -1
}
//封装
String.prototype._indexOf = Array.prototype._indexOf = function (searchVal, fromIndex) {
let data = this
let isArray = Array.isArray(data)
let isString = Object.prototype.toString.call(data) == '[object String]'
if (!isArray && !isString) throw new TypeError('String or Array')
if (isArray) return aIndexOf(data, searchVal, fromIndex)
if (isString) return sIndexOf(data, searchVal, fromIndex)
}
十二、实现一个Array.prototype.reduce
Array.prototype.reduce语法
Array.prototype.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
数组的reduce方法接收两个参数
- 第一个参数为一个函数,函数有四个参数,是accumulator、currentValue、index、array。分别代表:
accumulator
:累加器,累加回调函数的返回值。
currentValue
:当前元素。
index
:当前元素的索引。
array
:当前元素所属的数组对象。- 第二个参数为可选参数
initialValue
,代表传递给函数的初始值。
实现
Array.prototype.myReduce = function(callback, initialValue) {
let accumulator = initialValue ? initialValue : this[0];
for (let i = initialValue ? 0 : 1; i < this.length; i++) {
let _this = this;
accumulator = callback(accumulator, this[i], i, _this);
}
return accumulator;
};
// 使用
let arr = [1, 2, 3, 4];
let sum = arr.myReduce((prev, next) => {
prev += next
return prev
}, 5)
console.log(sum); // 15