前端面试手写大全(一)
1.排序
冒泡排序
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
var arr = [9, 6, 8, 5, 7, 4, 3, 1, 2]
// 排序:冒泡排序 那相邻的两个数进行比较,得到最大/小的一个数
function bubbleSort(arr) {
for (var i = 0; i < arr.length; i++) { // 比较length轮
for (var j = i; j < arr.length; j++) { // 确定最小的位就不再比较
if (arr[i] > arr[j]) {
var temp = arr[i] // 交换两个数
arr[i] = arr[j]
arr[j] = temp
console.log("交换")
} `
}
console.log(arr)
}
return arr
}
bubbleSort(arr)
</script>
</body>
</html>
快速排序
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
var arr = [9, 6, 8, 5, 7, 4, 3, 1, 2]
// 快速排序:
// 找到中间位,比中间位小的放左边,比中间大的放右边
function quickSort(arr){
if(arr.length<1){
return arr
}
var index = Math.floor(arr.length/2)
var center = arr.splice(index,1)[0]
var left = []
var right = []
for(var i = 0;i<arr.length;i++){
if(arr[i]<center){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
console.log(left,center,right)
return quickSort(left).concat(center,quickSort(right))
}
var re = quickSort(arr)
console.log("结果:",re)
</script>
</body>
</html>
2.ajax
ajax是什么
- 即异步的JavaScript 和XML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页
实现过程
- 实现 Ajax异步交互需要服务器逻辑进行配合,需要完成以下步骤
- 创建 Ajax的核心对象 XMLHttpRequest对象
通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接
构建请求所需的数据内容,并通过XMLHttpRequest 对象的 send() 方法发送给服务器端
通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态
接受并处理服务端向客户端响应的数据结果
将处理结果更新到 HTML页面中
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
// 同步,异步 post与get restFul http请求过程 http响应码
// 01:同步:按顺序从上至下执行阻塞式代码 异步:先执行主线程代码,再去执行非主线程的代码
// 实现异步:回调函数,事件响应函数,订阅与发布模式,promsie sync与await
// ajax默认是异步的,可以
function ajax(url,method,data,config){
return new Promise((resovle,reject)=>{
var xhr = new XMLHttpRequest()
// 打开
xhr.open(method,url)
if(config&&config.headers){
// 设置请求头
for(var k in config.headers){
xhr.setRequestHeader(k,config.headers[k])
}
}
// 等待状态发生变化
xhr.onreadystatechange = function(){
if(xhr.readyState===4){
// 成功
if(xhr.status===200){
resovle(JSON.parse(xhr.responseText))
}else{
// 失败
reject(xhr)
}
}
}
xhr.send(data)
})
}
ajax(url)
.then(res=>{
console.log(res)
})
.catch(err=>{
console.log(err)
})
// 02:post与get
// get:数据大小 post没有限制
// get2k post不缓存
//post通常用来修改,新增,删除 get通常用来获取
//restFul是一种程序设计规范,通常每一个url都是一个资源,可以通过get方法来获取post新增,put修改,delect删除
// http请求过程:在
// 01 浏览器通过域名找到ip
// 02 建立tcp链接,发送请求体与请求头
// 03 服务响应响应体,响应头,
// 04 浏览器解析html 生成dom树(下载css与js)
// 05 解析css 生成css树
// 06 css树与dom树合成为渲染树进行渲染(遇到js先执行js)
// 响应码
// 1开头准备
// 2开头成功 200成功 201创建成功 307重定向 404找不到 401没有权限 500服务器错误
// 3开头重定向
// 4开头客户端错误
// 5开头 服务器错误
</script>
</body>
</html>
3.jsonp
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
// 什么是同源 请求的地址与当前页面地址,域名,子域名,端口号一致
// 怎么跨域 jsonp,代理,cors响应头允许,
// jsonp的原理, src没有同源策略
// 统计功能用图片的src没有同源限制
function jsonp(url){
return new Promise((resolve,reject)=>{
// 创建一个src标签
var script = document.createElement("script")
// 定义回调函数
var fun = "callback"+Date.now()
// 拼接url
var src = ""
if(url.includes("?")){
src = url+"&callback="+fun
}else{
src = url+"?callback="+fun
}
// 插入到body
script.src = src
document.body.appendChild(script)
// 当执行回调函数返回数据
window[fun]=function(data){
resolve(data)
document.body.removeChild(script)
}
script.onerror = function(err){
reject(err)
document.body.removeChild(script)
}
})
}
jsonp("url")
.then(res=>{
console.log(res)
})
.catch(err=>console.log(err))
</script>
</body>
</html>
4.防抖节流
- 防抖节流是优化高频率执行代码的一种手段,如:浏览器的 resize、scroll、keypress、mousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能
- 为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用 防抖(debounce) 和 节流(throttle) 的方式来减少调用频率
- 一个经典的比喻:想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应,假设电梯有两种运行策略 debounce 和 throttle,
- 超时设定为15秒,不考虑容量限制,电梯第一个人进来后,15秒后准时运送一次,这是节流;
- 电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖
代码实现:
节流
// 节流
// 完成节流可以使用时间戳与定时器的写法
//使用时间戳写法,事件会立即执行,停止触发后没有办法再次执行
function throttled1(fn, delay = 500) {
let oldtime = Date.now()
return function (...args) {
let newtime = Date.now()
if (newtime - oldtime >= delay) {
fn.apply(null, args)
oldtime = Date.now()
}
}
}
//使用定时器写法,delay毫秒后第一次执行,第二次事件停止触发后依然会再一次执行
function throttled2(fn, delay = 500) {
let timer = null
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay);
}
}
}
//可以将时间戳写法的特性与定时器写法的特性相结合,实现一个更加精确的节流。实现如下
function throttled(fn, delay) {
let timer = null
let starttime = Date.now()
return function () {
let curTime = Date.now() // 当前时间
let remaining = delay - (curTime - starttime) // 从上一次到现在,还剩下多少多余时间
let context = this
let args = arguments
clearTimeout(timer)
if (remaining <= 0) {
fn.apply(context, args)
starttime = Date.now()
} else {
timer = setTimeout(fn, remaining);
}
}
}
防抖
//防抖
// 简单版本
function debounce(func, wait) {
let timeout;
return function () {
let context = this; // 保存this指向
let args = arguments; // 拿到event对象
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
// 防抖如果需要立即执行,可加入第三个参数用于判断,实现如下:
function debounce(func, wait, immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout); // timeout 不为null
if (immediate) {
let callNow = !timeout; // 第一次会立即执行,以后只有事件执行后才会再次触发
timeout = setTimeout(function () {
timeout = null;
}, wait)
if (callNow) {
func.apply(context, args)
}
}
else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
}
}
}
防抖和节流的区别
- 相同点
都可以通过使用 setTimeout 实现
目的都是,降低回调执行频率。节省计算资源 - 不同点
函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout和 setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能
函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次
应用场景
- 防抖在连续的事件,只需触发一次回调的场景有:
搜索框搜索输入。只需用户最后一次输入完,再发送请求
手机号、邮箱验证输入检测
窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。 - 节流在间隔一段时间执行一次回调的场景有:
滚动加载,加载更多或滚到底部监听
搜索框,搜索联想功能
5.bind
- bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向
- 那么什么情况下需要改变this的指向呢?下面举个例子
var name = "lucy";
var obj = {
name: "martin",
say: function () {
console.log(this.name);
}
};
obj.say(); // martin,this 指向 obj 对象
setTimeout(obj.say,0); // lucy,this 指向 window 对象
- 从上面可以看到,正常情况say方法输出martin,但是我们把say放在setTimeout方法中,在定时器中是作为回调函数来执行的,因此回到主栈执行时是在全局执行上下文的环境中执行的,这时候this指向window,所以输出lucy.我们实际需要的是this指向obj对象,这时候就需要该改变this指向了
setTimeout(obj.say.bind(obj),0); //martin,this指向obj对象
- bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入),改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
Function.prototype.myBind = function(context,...args){
// 获取上下文,第一个参数
context = context?context:window
// 定义一个符号
var sy = Symbol
// 把this挂载在context 上下文
context[sy] = this
return function(...args){
var list = [...args,...arguments]
// 返回函数,函数执行 执行context[sy]方法
context[sy](list)
delete context[sy]
}
}
var f = fun.myBind({
myname:"张三"
})
f(4,6)
6.call
- call方法的第一个参数也是this的指向,后面传入的是一个参数列表
- 跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
fn.call(obj,1,2); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window
- 同样的,当第一个参数为null、undefined的时候,默认指向window(在浏览器中)
// call apply bind 区别
// call与apply都是执行一个函数,第一个参数冒充this,bind冒充this 并产生一个新的函数
Function.prototype.myCall = function(context, ...args) {
var obj = context || window
// obj.fn = this
var sy = Symbol("特殊符号唯一");// 符号是唯一得,也可以用作对象得键名
// console.log(this) // 缓存this
// 执行this(执行函数)
// obj.fn(...args)
// delete obj.fn // 删除给obj新增得fn属性
obj[sy] = this
obj[sy](...args)
delete obj[sy]
}
function fun(a, b) {
console.log(this, a, b)
}
// 在原型挂载,所有函数都拥有myCall方法
// fun.myCall({
// name: "zzz",
// age: 18
// }, 5, 3)
7.new操作符
一、是什么
在JavaScript中,new操作符用于创建一个给定构造函数的实例对象
例子
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(this.name)
}
const person1 = new Person('Tom', 20)
console.log(person1) // Person {name: "Tom", age: 20}
t.sayName() // 'Tom'
从上面可以看到:
- new 通过构造函数 Person 创建出来的实例可以访问到构造函数中的属性
- new 通过构造函数 Person 创建出来的实例可以访问到构造函数原型链中的属性(即实例与构造函数通过原型链连接了起来)
- 现在在构建函数中显式加上返回值,并且这个返回值是一个原始类型
function Test(name) {
this.name = name
return 1
}
const t = new Test('xxx')
console.log(t.name) // 'xxx'
可以发现,构造函数中返回一个原始值,然而这个返回值并没有作用
下面在构造函数中返回一个对象
function Test(name) {
this.name = name
console.log(this) // Test { name: 'xxx' }
return { age: 26 }
}
const t = new Test('xxx')
console.log(t) // { age: 26 }
console.log(t.name) // 'undefined'
从上面可以发现,构造函数如果返回值为一个对象,那么这个返回值会被正常使用
二、流程
从上面介绍中,我们可以看到new关键字主要做了以下的工作:
创建一个新的对象obj
将对象与构建函数通过原型链连接起来
将构建函数中的this绑定到新建的对象obj上
根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
举个例子:
function Person(name, age){
this.name = name;
this.age = age;
}
const person1 = new Person('Tom', 20)
console.log(person1) // Person {name: "Tom", age: 20}
t.sayName() // 'Tom'
流程图如下:
三、手写new操作符
// new 关键字
// 01 创建一个对象
// 02 把this传入执行构造函数
// 03 修改对象
function mynew(fun,...args){
let obj = {}
fun.call(obj,...args)
obj.__proto__.constructor = fun
return obj
}