1.实现一个函数printNum,接受一个参数n,输出n个递增自然数 ·
输出的自然数不能含有7(17,71,176)或为7的倍数(14,63);如果含有7或为7的倍数,则输出下一个自然数;
· 支持多次调用,从0开始,每次自上次调用的末尾自然数继续打印
· 例子:
printNum(); // 0
printNum(2); // 1,2
printNum(5); // 3,4,5,6,8
printNum(17); // 9,10,11,...,26,29,30
复制代码
思路:
1.对参数为undefined特殊处理
2.输入的参数为输出数字的长度
3.对数字进行判断不能含有7或7的倍数
4.需要对每次最后输出的值进行记忆
5.将数组转为字符串输出
复制代码
考察知识点:
1.数组方法的使用
2.正则基本功
复制代码
代码:
let lastNum = 0
function printNum (num) {
if (!num) {
return 0
}
let arr = []
for (let i = 0; i < num; i++ ) {
lastNum ++
if (lastNum % 7 === 0 || /[7]/.test(lastNum) ) {
num ++
// 规避了 27 28这种情况
}
if (lastNum % 7 !== 0 && /[7]/.test(lastNum) ) {
arr.push(lastNum)
}
}
return arr.toString()
}
复制代码
最终输出结果与题目一致,但此解法并不完美,1.判断了两次7的倍数和含有7的情况 2.动态改变了for循环的num值
复制代码
优化
let i = -1
function printNum (n) {
let res = []
res.push(++i)
while(n > 1) {
i++
if (i % 7 != 0 && !(/[7]/.test(i))) {
res.push(i)
n--
}
}
return res.join(',')
}
复制代码
用闭包实现
function printNum (n) {
let i = -1
return function temp (n) {
let res = []
++i
if (i % 7 === 0 || /[7]/.test(i)) {
printNum(n)
}
res.push(i)
if (n < 0) {
return res.push(i)
}
while (n > 1) {
i++
if (i % 7 !== 0 && !(/[7]/.test(i)) {
res.push(i)
n--
}
}
return res.join(',')
}
}
复制代码
2.实现一个函数,接受一个url数组作为参数,返回一个Promise。
- 要求同时发送请求获取资源内容(发送请求使用Fetch,fetch函数说明:fetch()必须接受一个参数-----资源的路径。无论请求成功与否,它都返回一个promise对象,resolve对应请求的Response)
- 当任意一个资源加载成功(通过请求的status状态即可)则将promise的状态置为resolve,并将该url作为resolve的参数
- 当所有的资源都不成功时,promise状态置为reject
题目解析(感觉这道题目不是重点)
1.参数为url数组
2.同时发送请求(让我想到了预加载图片、上传多个文件)
复制代码
- 写出如下输出值
this.a = 20
var test = {
a: 40,
init: () => {
console.log(this.a)
function go () {
console.log(this.a)
}
go.prototype.a = 50
return go
}
}
var p = test.init() // 20 先执行 . 再执行 ()
p() // 20
复制代码
箭头里的this指向顶级对象test的作用域,指向window
this.a = 20
var test = {
a: 40,
init: function () {
console.log(this.a)
function go () {
console.log(this.a)
}
go.prototype.a = 50
return go
}
}
test.init() // 40
复制代码
var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => console.log(this), false);
}
}
handler.init() // 调用打印的this是hander对象
var handler = {
id: '123456',
init: ()=> {
console.log(this)
}
}
handler.init() // 调用打印的this是window
复制代码
3.分别使用递归和迭代的方式实现斐波那契数列值的计算: F(0) = 0,F(1) = 1,F(n) = F(n - 1) + F(n - 2);(n>=2, n为正整数)
// 递归
function fib (n) {
if (n == 1 || n == 2) {
return 1
}
return fib(n-1) + fib(n-2)
}
fib(10) // 55
复制代码
时间复杂度为O(2^n),空间复杂度为O(n)
// 非递归
function fib (n) {
var res = [1, 1]
if (n === 1 || n ===2) {
return 1
}
for (var i = 2; i < n; i++) {
res[i] = res[i-1] + res[i-2]
}
return res[n-1]
}
fib(10) // 55
复制代码
时间复杂度为O(n),空间复杂度为O(n)
for循环版本(递归有性能问题,用循环来做。)
function fibonacci (n) {
var last = 1
var last2 = 0
var current = last2
for (var i = 1; i <= n; i++) {
last2 = last
last = current
current = last + last2
}
return current
}
复制代码
// 非递归
function fib (n) {
var a, b ,res;
a = b = 1;
for (var i = 3;i <= n; i++) {
res = a + b
a = b
b = res
}
return res
}
复制代码
时间复杂度为O(n),空间复杂度为O(1)
function fibon(a,b,n) {
if (n<=2){
return a;
}
return fibon(a+b,a,n-1);
}
console.log(fibon(1,1,10));//求数列中第十个数
复制代码
4.bind 看官方的polyfill
- bind
- arguments
- 原型
if (!Function.prototype.bind) {
Function.prototype.bind = function (onThis) {
if(typeof this != "function") {
throw new Error("请使用函数调用bind")
}
var args = Arrays.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
console.log('args', args)
const _args = args.concat(Array.prototype.slice.call(arguments))
// bind 核心, this instanctof fBound == true的时候,说明返回的fBound被当做new的构造函数调用
// 模拟原生bind的多个参数
return fToBind.apply( this instanctof fBound ? this : onThis, _args)
}
// 维护原型链
if (this.prototype) {
fNOP.prototype = this.prototype
}
// 同一个堆地址 fBound.prototype = this.prototype 是错误的
// 修正函数名和构造函数
fBound.prototype = new fNOP()
return fBound
}
}
复制代码
this.age = 18
function people (data,data1) {
this.cons = "构造函数"
console.log(this.age)
console.log(data)
console.log(data1)
}
test.prototype.init = function () {
console.log()
}
var result = test.bind(this, "Fruit")
var result1 = new result("Fruit Bro")
复制代码
5.异步错误能被捕获到吗
第一种
try {
console.log(1)
setTimeout(() => {
console.log(2)
}, 100);
setTimeout(() => {
console.log(3)
throw new Error(5)
}, 0);
console.log(4)
} catch (error) {
console.log(`错啦 ${error}`)
}
复制代码
第二种
try {
console.log(1)
setTimeout(() => {
console.log(2)
}, 100);
console.log(3)
throw new Error(5)
console.log(4)
} catch (error) {
console.log(`错啦 ${error}`)
}
复制代码
自己先猜想一下吧,然后再控制台验证一下自己的猜想
执行结果如下
- 第一种
分析:运行中直接抛出了错误,而没有进入catch,说明异常中的错误未被正常捕获到,那么,问题来了,对于异步的错误改如何处理呢?欢迎说出你的想法 - 第二种
可以发现,正常打印了 【错啦 Error: 5】说明错误被catch捕获到了,对于同的错误的捕获没有任何问题
6.实现函数柯里化
function composeFunctions () { var args = Array.prototype.slice.apply(arguments)
var _func = fucntion () {
if (arguments.length === 0) {
return func.apply(this, args)
}
Array.prototype.push.apply(args, arguments)
return _func
}
return _func
复制代码
}
7.简短优雅地实现sleep函数
async function test() {
console.log('hello')
await sleep(1000)
console.log('world')
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
test()
复制代码
8.防抖
防抖和节流的作用都是防止函数都次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的情况会每隔一定时间(参数wait)调用函数。
// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
// 缓存一个定时器id
let timer = 0
// 这里返回的函数是每次用户实际调用的防抖函数
// 如果已经设定过定时器了就清空上一次的定时器
// 开始一个新的定时器,延迟执行用户传入的方法
return function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
复制代码
使用
function handleClick (...args) {
//...
}
let a = debounce(handleClick, 1000)
// 每次执行时调用a就可以了
复制代码
来实现一个带有立即执行选项的防抖函数
例如用户点star的时候,我们希望用户点第一下的时候就去调用接口,并且成功之后改变star按钮的样子,用户就可以立马得到反馈是否star成功了,这个情况适用立即执行的防抖函数,它总是在第一次调用,并且下一次调用必须与前一次调用的时间间隔大于wait才会触发。
// 获取当前时间戳
function now () {
return +new Date()
}
/**
* 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {boolean} immediate 设置为ture时,是否立即调用函数
* @return {function} 返回客户调用函数
*/
function debounce (func, wait = 50, immediate = true) {
let timer, context, args
// 延迟执行函数
const later = () => setTimeout(() => {
// 延迟函数执行完毕,清空缓存的定时器序号
timer = null
// 延迟执行的情况下,函数会在延迟函数中执行
// 使用到之前缓存的参数和上下文
if (!immediate) {
func.apply(context, args)
context = args = null
}
}, wait)
// 这里返回的函数是每次实际调用的函数
return function(...params) {
// 如果没有创建延迟执行函数(later),就创建一个
if (!timer) {
timer = later()
// 如果是立即执行,调用函数
// 否则缓存参数和调用上下文
if (immediate) {
func.apply(this, params)
} else {
context = this
args = params
}
// 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个
// 这样做延迟函数会重新计时
} else {
clearTimeout(timer)
timer = later()
}
}
}
复制代码
总结:
- 对于按钮防点击来说的实现:如果函数是立即执行的,就立即调用,如果函数式延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦我开始一个定时器,只要我定时器还在,你每次点击我都重新计时。一旦你点累了,定时器时间到,定时器重置为null,就可以再次点击了。
- 对于延时执行函数来说的实现:清除定时器ID,如果是延迟调用就调用函数。
9.节流
防抖和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
/**
* underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。
* 如果想忽略结尾函数的调用,传入{trailing: false}
* 两者不能共存,否则函数不能执行
* @return {function} 返回客户调用函数
*/
_.throttle = function(func, wait, options) {
var context, args, result
var timeout = null
// 之前的时间戳
var previous = 0
// 如果 options 没传则设为空对象
if (!option) options = {}
// 定时器回调函数
var later = function () {
// 如果设置了 leading,就将 previos 设为 0
// 用于下面函数的第一个 if 判断
previous = options.leading === false ? 0 : _.now()
// 置空一是为了防止内存泄漏,二是位了下面的定时器判断
timeout = null
result = func.apply(context, args)
if (!timeout) context = args = null
}
return function () {
// 获得当前时间戳
var now = _.now()
// 首次进入前者肯定为 true
// 如果需要第一次不执行函数
// 就将上次时间戳设为当前的
// 这样在接下来计算 remaining 的值时会大于0
if (!previous && options.leading === false) previous = now
// 计算剩余时间
var remaining = wait - (now - previous)
context = this
args = arguments
// 如果当前调用已经大于上次调用时间 + wait
// 或者用户手动调了时间
// 如果设置了 trailing,只会进入这个条件
// 如果没有设置 leading,那么第一次会进入这个条件
// 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
// 其实还是会进入的,因为定时器的延时
// 并不是准确的时间,很可能你设置了2秒
// 但是他需要2.2秒才触发,这时候就会进入这个条件
if (remaining <= 0 || remaining > wait) {
// 如果存在定时器就清理掉否则会调用二次回调
if (timeout) {
clearTimeout(timeout)
timeout = null
}
previous = now
result = func.apply(context, args)
if (!timeout) context = args = null
} esle if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
// 没有的话就开启一个定时器
// 并且不能不能同时设置 leading 和 trailing
timeout = setTimeout(later, remaining)
}
return result
}
}
复制代码
10.变量提升
test()
alert(a) // undefined
var flag = true
if (!flag) {
var a = 1
}
if (flag) {
function test () {
console.log('test1')
}
} else {
function test () {
console.log('test2')
}
}
复制代码
function test () {
console.log('1')
}
function init () {
test()
if (false) {
function test () {
console.log('2')
}
}
}
init()
复制代码
function func(flag) {
if (flag) {
function getValue() {
return 'a'
}
} else {
function getValue () {
return 'b'
}
}
return getValue()
}
console.log(func(true))
// 上面这段代码相当于
function functions(flag) {
function getValue(){return 'a'};
function getValue(){return 'b'};
if (flag) {
....
} else {
....
}
return getValue();
}
// 如何解决
function functions(flag) {
if (flag) {
var getValue = function () { return 'a'; }
} else {
var getValue = function () { return 'b'; }
}
return getValue();
}
// 用函数表达式的方法,因为函数表达式不会提升,因此只有当逻辑执行到这儿的时候才会执行。避免了函数声明提升的问题。
复制代码
console.log(a) // f a() {}
var a = 1
function a () {
}
console.log(a) // 1
复制代码
11.在一个数组中找出和为m的两个数,拓展一下,一个数组中找出n个数,使其和为m
假设:找出和为10的两个数
const arr = [5, 6, 2, 1, 7, 9, 4, 8]
解法一: 穷举
1.将数组排序
2.while循环
const arr = [5, 6, 2, 1, 7, 9, 4, 8]
function sortArr (arr) {
return arr.sort((a, b) => {
return a - b
})
}
function getSumNum (arr, Sum) {
let newArr = sortArr(arr)
let i = 0
let j = arr.length -1
while (i < j) {
if (i === j) return
if (newArr[i] + newArr[j] === Sum) {
console.log( `${newArr[i]} 和 ${newArr[j]}` )
i++
j--
} else if (newArr[i] + newArr[j] < Sum) {
i++
} else {
j--
}
}
return 'none'
}
// 1 和 9
// 2 和 8
// 4 和 6
// none
复制代码