前端编程题:
1.节流和防抖
函数节流:一个函数执行一次后,只有大于设定的执行周期后才会执行第二次
function throttle(fn, delay) {
// 记录上一次函数触发的时间
var lastTime = 0;
return function() {
// 记录当前函数触发的时间
var nowTime = Date.now();
if (nowTime - lastTime > delay) {
// 修正this指向问题
fn.call(this);
// 同步时间
lastTime = nowTime;
}
}
}
作者:浪里行舟
链接:https://juejin.im/post/6844903727900409870
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
function throttle(fn, interval = 300) {
let canRun = true;
return function () {
if (!canRun) return;
canRun = false;
setTimeout(() => {
fn.apply(this, arguments);
canRun = true;
}, interval);
};
}
函数的节流就是通过闭包保存一个标记(canRun = true),在函数的开头判断这个标记是否为 true,如果为 true 的话就继续执行函数,否则则 return 掉,判断完标记后立即把这个标记设为 false,然后把外部传入的函数的执行包在一个 setTimeout 中,最后在 setTimeout 执行完毕后再把标记设置为 true(这里很关键),表示可以执行下一次的循环了。当 setTimeout 还未执行的时候,canRun 这个标记始终为 false,在开头的判断中被 return 掉
防抖函数:一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效。
function debounce(fn, delay) {
// 记录上一次的延时器
var timer = null;
return function() {
// 清除上一次延时器
clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(this)
}, delay)
}
}
作者:浪里行舟
链接:https://juejin.im/post/6844903727900409870
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
function debounce(fn, interval = 300) {
let timeout = null;
return function () {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, interval);
};
}
其实函数防抖的原理也非常地简单,通过闭包保存一个标记来保存 setTimeout 返回的值,每当用户输入的时候把前一个 setTimeout clear 掉,然后又创建一个新的 setTimeout,这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数了。
2.深拷贝
3.js的连续赋值
来自:https://www.cnblogs.com/Gavin257/p/9562214.html
首先来一个经典案例:
var a = {
n: 1};
var b = a;
a.x = a = {
n: 2};
console.log(a.x); //undefined
console.log(b.x); //{n: 2}
说明:
1)此处的a,b是引用类型
2)在javascript中字段访问操作符".“的优先级高于赋值操作符”="
3)出现多个赋值操作符"="时,运算顺序为从右向左
第一行和第二行执行后:a和b同时指向同一地址(存放对象{n:1}
第三行最先执行的是a.x
第三行然后执行:
a = {n: 2},使得a指向另一个地址,让a指向对象{n:2}的引用
第三行最后一步:就是最左边的赋值
(a.x )= (a = {n: 2});
实际上是给对象
{
n:1
x:undefined
}
中的x赋值,
{
n:1
x:{n:2}
}
总结:在运行完上述命令后,变量a指向了新对象{n : 2};变量b 的地址没有发生改变,因而仍指向修改后的对象{n: 1;x: {n : 2}}。
4.闭包和作用域
来自:https://www.baidu.com/link?url=EQyHYLvrPKsw4ekagYIjv7aThqd0zfpWN_ptzT0E-xnQCbvvi_tICqqg5R0GyiyAYmr-8rffvjgsn_bHd1OkSa&wd=&eqid=b6ccc59500199d1d000000065f425808
var a = 0,
b = 0;
function A(a) {
A = function (b) {
console.log(a + b++)
}
console.log(a++)
}
A(1)//1
A(2)//4
闭包机制:闭包创建后,可以保存创建时的活动对象。
自加操作符:++,当++作为后缀操作符时,调用++的表达式的值为自加前的自加对象的值。
此处说明 ++操作符的特性。
var i = 0;
var eg = i++
console.log(i, eg) // 1 0
第一次调用A时,执行到console.log(a++)时,a已经完成自加,此时a的值为2,a++的值为1。
A(1)=1
第二次调用A时,A已经被重新赋值,指向了一个新的函数引用;
由于标识符A是在全局作用域定义的,所以在函数内部被重新赋值,在全局作用域也可以访问到重新赋值后的函数。
此时,也创建了一个闭包,该闭包保存了创建环境的活动对象。
此时活动对象:{ a: 2 },同时,根据传入的数值2,确定 b = 2,b++值为3。
执行到 console.log(a + b++),故而输出4
4.实现new 操作符
来自:https://blog.csdn.net/q1424966670/article/details/92839918
要手动实现一个 new 操作符,首先要知道 new 操作符都做了什么事,即构造函数的内部原理:
1.创建一个新对象;
2.链接到原型(将构造函数的 prototype 赋值给新对象的 __proto__);
3.绑定this(构造函数中的this指向新对象并且调用构造函数)
4.返回新对象
这样我们就可以手动实现一个 new 方法了
function realizeNew () {
//创建一个新对象
let obj = {
};
//获得构造函数
let Con = [].shift.call(arguments);
//链接到原型(给obj这个新生对象的原型指向它的构造函数的原型)
obj.__proto__ = Con.prototype;
//绑定this
let result = Con.apply(obj,arguments);
//确保new出来的是一个对象
return typeof result === "object" ? result : obj
}
我们实现的 realizeNew() 方法需要传入的参数是:构造函数 + 属性
1.首先我们创建一个新对象,
2.然后通过 arguments 类数组获取构造函数和其他参数
我们可以知道参数中包含了构造函数以及我们调用create时传入的其他参数,接下来就是要想如何得到其中这个构造函数和其他的参数,由于arguments是类数组,没有直接的方法可以供其使用,我们可以有以下两种方法:
1.Array.from(arguments).shift(); 转换成数组 使用数组的方法 shift 将第一项弹出
2.[].shift().call(arguments) ; 通过 call() 让arguments能够借用shift()方法
绑定this的时候需要注意:
1.给构造函数传入属性,注意构造函数的this属性
2.参数传进 Con 对 obj 的属性赋值,this要指向 obj 对象
3.在 Con 内部手动指定函数执行时的this 使用call、apply实现
4.最后我们需要返回一个对象
我们来测试一下:
function Person (name,age){
this.name = name;
this.age = age;
this.say = function () {
console.log("I am " + this.name)
}
}
//通过new创建构造实例
let person1 = new Person("Curry",18);
console.log(person1.name); //"Curry"
console.log(person1.age); //18
person1.say(); //"I am Curry'
//通过realize()方法创造实例
let person2 = realizeNew (Person,"Curry",18);
console.log(person2.name); //"Curry"
console.log(person2.age); //18
5.Js – 函数柯里化
来自:https://blog.csdn.net/weixin_37680520/article/details/108371908
https://www.cnblogs.com/plBlog/p/12356042.html
维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视
感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个
参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
看这个解释有一点抽象
1.我们就拿被做了无数次示例的add函数,来做一个简单的实现。
// 普通的add函数
复制代码
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
实际上就是把add函数的x,y两个参数变成了先用一个函数接收x然后返回一个函数去处理y参数。现在思路应该就比较清晰了,就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
但是问题来了费这么大劲封装一层,到底有什么用处呢?没有好处想让我们程序员多干事情是不可能滴,这辈子都不可能.
来列一列Currying有哪些好处呢?
2. 参数复用
// 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}
check(/\d+/g, 'test') //false
check(/[a-z]+/g, 'test') //true
// Currying后
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
上面的示例是一个正则的校验,正常来说直接调用check函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数reg进行复用,这样别的地方就能够直接调用hasNumber,hasLetter等函数,让参数能够复用,调用起来也更方便。
3.实现一个函数功能:sum(1,2,3,4…n)转化为 sum(1)(2)(3)(4)…(n)
// 使用柯里化 + 递归
注意:length 是js函数对象的一个属性值,该值是指 “该函数有多少个必须要传入的参数”,
function curry ( fn ) {
var c = (...arg) => (fn.length === arg.length) ?
fn (...arg) : (...arg1) => c(...arg, ...arg1)
return c
}
var a=function(a,b,c){
return a+b+c}
undefined
function curry ( fn ) {
var c = (...arg) => (fn.length === arg.length) ?
fn (...arg) : (...arg1) => c(...arg, ...arg1)
return c
}
undefined
var b=curry(a)
undefined
a(1,2,3)
6
b(1)(2)(3)
6
a.length
3
curry(sum)
4.实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function() {
_args.push(...arguments);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
add(1)(2)(3) // 6
add(1, 2, 3)(4) // 10
add(1)(2)(3)(4)(5) // 15
add(2, 6)(1) // 9
6.实现一个Array.prototype.flat()函数
1.方式1:
Array.prototype.myFlat = function(arr=[]) {
if (Array.isArray(this)) {
this.forEach(item => {
if(Array.isArray(item)){
item.myFlat(arr)
} else {
arr.push(item)
}
});
return arr;
} else {
throw tihs + ".flat is not a function";
}
};
ƒ (arr1) {
if (Array.isArray(this)) {
let arr=arr1&&arr1.length>0?arr1:[];
this.forEach(item => {
if(Array.isArray(item)){
item.myFlat(arr)
} else {
arr.push…
array.myFlat()
(7) [1, 2, 3, 4, 5, 6, 7]
var array=[1,[2,3],4,[5,6,7],[8,9,10,11,[12,13,
array.myFlat()
(20) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
2.升级版本:可以控制解开多少层。
Array.prototype.myFlat = function(num = 1) {
if (Array.isArray(this)) {
let arr = [];
if (!Number(num) || Number(num) < 0) {
return this;
}
this.forEach(item => {
if(Array.isArray(item)){
let count = num
arr = arr.concat(item.myFlat(--count))
} else {
arr.push(item)
}
});
return arr;
} else {
throw tihs + ".flat is not a function";
}
};
链接:https://zhuanlan.zhihu.com/p/108289604?utm_source=qq&utm_medium=social&utm_oi=951563727821062144
3.简洁写法:
function flatDeep(arr) {
return arr.reduce((res, cur) => {
if(Array.isArray(cur)){
return [...res, ...flatDeep(cur)]
}else{
return [...res, cur]
}
},[])
}
var array=[1,[2,3],4,[5,6,7],[8,9,10,11,[12,13,[14,15,16],17,18],[19,20],]]
flatDeep(array)
(20) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
链接:https://juejin.im/post/6864398060702760968
7.数据双向绑定
来自:https://zhuanlan.zhihu.com/p/123012477
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<input type="text" id="input" />
<br>
<!-- span的值为:<span id="span"></span> -->
<script>
// 数据
data = {
text: 'defau44lt'
};
const input = document.getElementById('input');
// const span = document.getElementById('span');
// 数据劫持
Object.defineProperty(data, 'text', {
// 数据变化 —> 修改视图
set(newVal) {
input.value = newVal;
// span.innerHTML = newVal;
},
get: function () {
var value=document.getElementById('input').value
return value
},
});
// 视图更改 --> 数据变化
input.addEventListener('keyup', function (e) {
data.text = e.target.value;
console.log(data.text);
});
</script>
</body>
</html>
8.头条面试题-创建一个Event类,并创建on、off、trigger、once方法
来自:https://www.cnblogs.com/hyshi/p/10918500.html
版本1:
一、创建一个Event.js
复制代码
class Event {
constructor() {
this.handlers = {
// 记录所有的事件和处理函数
}
}
/* *
* on 添加事件监听
* @param type 事件类型
* @param handler 事件回调
* on('click', ()=>{})
* */
on(type, handler, once=false) {
if (!this.handlers[type]) {
this.handlers[type] = [];
}
if (!this.handlers[type].includes(handler)) {
this.handlers[type].push(handler);
handler.once = once;
}
}
/* *
* off 取消事件监听
*
* */
off(type, handler) {
if (this.handlers[type]) {
if (handler === undefined) {
this.handlers[type] = []
} else {
this.handlers[type] = this.handlers[type].filter((f)=>{
return f!=handler
})
}
}
}
/* *
* @param type 要执行哪个类型的函数
* @param eventData事件对象
* @param point this指向
*
* */
trigger(type, eventData = {
}, point=this) {
if (this.handlers[type]) {
this.handlers[type].forEach(f => {
f.call(point, eventData);
if (f.once) {
this.off(type, f)
}
});
}
}
/* *
* once 函数执行一次
* @param type 事件处理
* @param handle 事件处理函数
* */
once(type, handler) {
this.on(type, handler, true);
}
}
复制代码
二、使用Event.js
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#box {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: red;
}
</style>
<script src="./event.js"></script>
</head>
<body>
<div id="box"></div>
<script>
/*
* 1.记录摁下时鼠标的位置和元素位置
* 鼠标位置-摁下时的鼠标位置 = 鼠标移动的位置
* 元素位置=鼠标移动距离+摁下时元素位置
**/
class Drag extends Event{
// 构造函数
constructor(el) {
super(); // 继承
this.el = el;
this.startOffset = null; // 鼠标摁下时元素的位置
this.startPoint = null; // 鼠标的坐标
let move = (e)=>{
this.move(e)
}
let end = (e)=>{
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', end);
this.end(e)
}
el.addEventListener('mousedown', (e)=> {
this.start(e);
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', end);
})
}
start(e) {
let {
el} = this;
console.log(this)
console.log(el)
this.startOffset = {
x: el.offsetLeft,
y: el.offsetTop
}
this.startPoint = {
x: e.clientX,
y: e.clientY
}
this.trigger('dragstart', e, this.el)
}
end(e) {
this.trigger('dragend',e, this.el)
}
move(e) {
let {
el, startOffset, startPoint} = this;
let nowPoint = {
x: e.clientX,
y: e.clientY
}
let dis = {
x: nowPoint.x - startPoint.x,
y: nowPoint.y - startPoint.y
}
el.style.left = dis.x + startOffset.x + 'px';
el.style.top = dis.y + startOffset.y + 'px';
this.trigger('dragmove', e, el)
}
}
(function() {
let box = document.querySelector('#box');
let dragBox = new Drag(box);
dragBox.on('dragstart', function(e) {
console.log(e);
console.log(this);
this.style.background = 'yellow';
})
dragBox.on('dragend', function(e) {
console.log('b')
this.style.background = 'blue';
})
dragBox.once('dragmove', function(e) {
console.log('c')
// this.style.background = 'blue';
})
console.log(dragBox)
})()
</script>
</body>
</html>
复制代码
参考第二个版本:https://juejin.im/post/6844903965646127117#heading-2
class EventEmitter {
constructor(){
this.events = {
}
}
on(name,cb){
if(!this.events[name]){
this.events[name] = [cb];
}else{
this.events[name].push(cb)
}
}
emit(name,...arg){
if(this.events[name]){
this.events[name].forEach(fn => {
fn.call(this,...arg)
})
}
}
off(name,cb){
if(this.events[name]){
this.events[name] = this.events[name].filter(fn => {
return fn != cb
})
}
}
once(name,fn){
var onlyOnce = () => {
fn.apply(this,arguments);
this.off(name,onlyOnce)
}
this.on(name,onlyOnce);
return this;
}
}
9.实现call,apply,bind
作者:white_give
链接:https://juejin.cn/post/6874901113062031367
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1)call的实现:
Function.prototype.myCall = function (ctx, ...args) {
ctx = ctx || window;
ctx.fn = this;
const result = ctx.fn(...args);
delete ctx.fn;
return result;
}
2)apply的实现
Function.prototype.myApply = function (ctx, args = []) {
if (args && !Array.isArray(args)) {
throw ('Uncaught TypeError: CreateListFromArrayLike called on non-object');
}
ctx = ctx || window;
ctx.fn = this;
const result = ctx.fn(...args);
delete ctx.fn;
return result;
}
3)bind的实现
Function.prototype.myBind = function (ctx, ...args1) {
const that = this;
const o = function () {
};
const newFn = function (...args2) {
const args = args1.concat(args2);
if (this instanceof o) {
that.apply(this, args);
} else {
that.apply(ctx, args);
}
}
o.prototype = that.prototype;
newFc.prototype = new o;
return newFn;
}
10.手写axios
a)先看看axios的使用:
getNewsList(){
this.axios.get('api/getNewsList').then((response)=>{
this.newsList=response.data.data;
}).catch((response)=>{
console.log(response);
})
}
b)实现
axios 原理还是属于 XMLHttpRequest, 因此需要实现一个ajax。
还需要但会一个promise对象来对结果进行处理。
以get请求为例,实现一个axios
实现ajax的get请求
var Ajax={
get: function(url, fn) {
// XMLHttpRequest对象用于在后台与服务器交换数据
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
// readyState == 4说明请求已完成
if (xhr.readyState == 4 && xhr.status == 200) {
// 从服务器获得数据
fn.call(this, xhr.responseText);
}
};
xhr.send();
}
}
封装Ajax,实现Axios进行回调
var Axios = {
get: function(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
// readyState == 4说明请求已完成
if (xhr.readyState == 4 && xhr.status == 200) {
// 从服务器获得数据
resolve(xhr.responseText)
}
};
xhr.send();
})
},
}
axios的使用如下: