JavaScript:原生JS实现图片懒加载

获取当前页面滚动条纵坐标的位置scrollTop

1
var heightTop = document.documentElement.scrollTop || document.body.scrollTop;

实现代码

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
58
59
60
61
62
63
64
65
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>LazyLoad</title>
<style type="text/css">
*{ margin:0; padding: 0; list-style: none; }
#list img{ width: 500px; height: 500px;}
</style>
</head>
<body>
<ul id="list">
<li><img _src="images/1.jpg" alt="pic"/></li>
<li><img _src="images/2.jpg" alt="pic"/></li>
<li><img _src="images/3.jpg" alt="pic"/></li>
<li><img _src="images/4.jpg" alt="pic"/></li>
<li><img _src="images/5.jpg" alt="pic"/></li>
<li><img _src="images/6.jpg" alt="pic"/></li>
<li><img _src="images/7.jpg" alt="pic"/></li>
<li><img _src="images/8.jpg" alt="pic"/></li>
</ul>

<script type="text/javascript">
<script>
// 获取图片
var oImg = document.getElementsByTagName('img');

fn(); // 先让第一张图展现

window.onscroll = function(){
fn(); // 滚轮滚动事件

};

function fn(){
//判断图片是否在可视区内
for(let i=0; i<oImg.length; i++){
let oImgTo = oImg[i].offsetTop;
//元素距离页面顶端的距离,当前元素顶部距离最近的"设置了position属性的"父元素的距离,如果没有设置的话就是针对于body顶部的距离
let clientH = document.documentElement.clientHeight;
//可视区的高度
let scrollT = document.documentElement.scrollTop || document.body.scrollTop;
//可视区顶部距离页面顶部的距离

if(clientH + scrollT >= oImgTo){
oImg[i].src = oImg[i].getAttribute('_src');
}
}
}
//getAttribute() 方法根据名称取得属性值。

// 函数防抖
function throttle(method,delay){
var timer = null;
return function(){
clearTimeout(timer);
timer=setTimeout(function(){
method.apply(this, arguments);
},delay);
}
}
window.onscroll = throttle(lazyload,200);
</script>
</body>
</html>

原生js实现ajax

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
// get方式

//第1步创建一个xhr对象,使用new关键来调用一个内置构造函数

const p = new Promise(resolve,reject) => {
const xhr = new XMLHttpRequest();

//第2步指定接收回来的内容,怎么处理。监听xhr对象的onreadystatechange事件,这个事件在xhr对象的“就绪状态”改变的时候触发。我们只关心就绪状态为4的时候的事情。
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status === 200) {
//接收完文件要做的事情,返回的数据为responseText
resolve(JSON.prase(xhr.responseText))
} else if(xhr.status === 404){
reject(new Error("404"))
}
}
};

//第3步创建一个请求,第一个参数是请求的类型get或者post,第二个参数就是请求的路径,第三个参数叫做是否使用异步机制
xhr.open("get","a.txt",true);

//第4步发送请求,圆括号里面是请求头内容,get请求没有报文头写null
xhr.send(null);
}
1
2
3
4
5
// 请求方式为post时
xhr.open("POST", options.url, true);
//设置表单提交时的内容类型
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);

扁平化和去重

扁平化

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
	// 方法五
function Flat5(arr){
var newArr =[];
for(let i in arr){
if(arr[i] instanceof Array){
newArr = newArr.concat(Flat5(arr[i]));
// newArr.push.apply(newArr, Flat5(arr[i]));
}else{
newArr.push(arr[i]);
}
}
return newArr;
}

Array.prototype.myFlat = function(num = 1) {
if (!Number(num) || Number(num) < 0) {
return this;
}
let arr = this.concat(); // 获得调用 myFlat 函数的数组
while (num > 0) {
if (arr.some(x => Array.isArray(x))) {
arr = [].concat.apply([], arr); // 数组中还有数组元素的话并且 num > 0,继续展开一层数组
} else {
break; // 数组中没有数组元素并且不管 num 是否依旧大于 0,停止循环。
}
num--;
}
return arr;
};

function flatDeep(arr, d = 1) {
return d > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), [])
: arr.slice();
};

arr.myFlat(Infinity);
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "前端收割机" }];


var arr1 = [[1, 2],[3, 4, 5], [6, 7, 8, 9]];
console.log(Flat1(arr1));
// (9) [1, 2, 3, 4, 5, 6, 7, 8, 9]

去重

1
2
3
4
5
6
7
8
9
10
11
12
13
const result = Array.from(new Set(originalArray));

const result = [];
for (let v of originalArray) {
if (!result.includes(v)) {
result.push(v);
}
}

// filter()法
let arr = [1, 2, 2, 3, 4, 5, 5, 6];
let newArr = arr.filter((x, index,self)=>self.indexOf(x)===index)
console.log(newArr)

深拷贝和浅拷贝

浅拷贝

1
2
const arr2 = [].concat(arr1);
// const arr2 = arr1.slice(0);

深拷贝

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
// 暴力法
// 无法拷贝对象方法,null,undefined
JSON.parse(JSON.stringify(a))


// lmran
function deepCopy (obj) {
if (obj === null || typeof obj !== 'object') {
return obj
}

let copy = Array.isArray(obj) ? [] : {}

Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key])
})

return copy
}

delete $obj.c.d
deepCopy($obj)

// 递归
function deepClone(obj) {
// 如果是值类型,则直接return
if(typeof obj !== 'object') {
return obj
}

// 定义结果对象
// let copy = {}

// 如果对象是数组,则定义结果数组
// if(obj.constructor === Array) {
// copy = []
// }

// 缩写
let copy = Array.isArray(obj)?[]:{};

// 遍历对象的key
for(let key in obj) {
// 如果key是对象的自有属性
if(obj.hasOwnProperty(key)) {
// 递归调用深拷贝方法
copy[key] = deepClone(obj[key])
}
}

return copy
}

事件委托

1
2
3
4
5
6
7
8
9
10
11
12
window.onload = function(){
  var oUl = document.getElementById("ul1");
  oUl.onclick = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
// 把这个节点的名字变成小写,因为html里面也可以大写的LI,所以这个只是为了统一名称
    if(target.nodeName.toLowerCase() == 'li'){
         alert(123);
        alert(target.innerHTML);
    }
  }
}

防抖节流

防抖

  • 事件被触发 n 秒后执行的回调 如果在这 n 秒内又触发 则重新计时
  • 函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条
1
2
3
4
5
6
7
8
9
10
function debounce(fun, delay) {
let timer;
return function() {
clearTimeour(timer)
timer = setTimeout(() => {
// 调用的时候传 this 否则 this 指向 window
fun.apply(this, arguments)
}, delay)
}
}

节流

  • 一事件在单位时间内 多次触发 仅一次有效
  • 函数节流就是fps游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹
1
2
3
4
5
6
7
8
9
10
11
function throttle(fn, delay) {
let canRun = true;
return function(){
if(!canRun) return;
canRun = false;
setTimeout(()=> {
fn.apply(this, arguments)
canRun = true;
}, delay)
}
}

Promise

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
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(err){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕获构造异常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}

然后还要挂载then调用

1
2
3
4
5
6
7
8
9
10
11
12
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:
}
}

Promise.all

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function diyPromiseAll(arr) {
// 这个是保存返回值的数组
let res_arr = [];
// 这个是当前遍历到的promise的下标
let promiseIndx = 0;
return new Promise((resolve, reject) => {
// 这里差不多是个递归函数,如果没有遍历到最后一个promise,那么将一致递归到最后一个为止
function dealPromise(){
if (promiseIndx === arr.length - 1) {
arr[promiseIndx].then(res => {
res_arr.push(res);
resolve(res_arr);
})
} else {
arr[promiseIndx].then(res => {
res_arr.push(res);
promiseIndx++;
dealPromise();
})
}
}
dealPromise();
})
}

promise.rece(): 顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

call、apply、bind、sleep

call后面传递的参数是以逗号的形式分开的,apply传递的参数是数组形式。(apply是以A开头,所以这里可以跟Array有关联,即参数为数组)
bind返回的是一个函数形式,如果要执行,则后面要再加一个小括号,例如:bind(obj,参数1,参数2)(),bind只能以逗号分隔形式,不能是数组形式。

bind

1
2
3
4
5
6
7
8
9
//  精简
Function.prototype.myBind = function(ctx=window, ...args1) {
let self = this
function bFn(...args2) {
return self.apply(this instanceof bFn ? this : ctx, args1.concat(args2))
}
bFn.prototype = this.prototype
return bFn
}

call

1
2
3
4
5
6
7
Function.prototype.myCall = function (ctx=window, ...args) {
ctx.fn = this
let result = ctx.fn(...args)
delete ctx.fn

return result
}

apply

1
2
3
4
5
6
7
Function.prototype.myApply = function (ctx=window, arr) {
ctx.fn = this
let result = ctx.fn(...arr)
delete ctx.fn

return result
}

new

1
2
3
4
5
6
7
8
9
10
11
12
function  myNew(fn, ...args) {
// let obj = {}
// Object.setPrototypeOf(obj, fn)

// 以构造函数fn的prototype为原型 创建一个新的简单对象
let obj = Object.create(fn.prototype);

// 改变fn的this指向到o,并执行fn
let result = fn.apply(obj, args)

return typeof result === 'object' ? result : obj
}

trim

1
2
3
String.prototype.trim=function(){
   return this.replace(/(^\s*)|(\s*$)/g, "");
   }

sleep

1
2
3
4
5
function sleep(duration) {
return new Promise(function (resolve) {
setTimeout(resolve, duration);
})
}

instantof

1
2
3
4
5
6
7
8
9
10
11
12
function myInstance(left, right) {
var proto = left.__proto__;
var prototype = right.prototype;

if (proto === null) {
return false;
} else if (proto === prototype) {
return true;
} else {
return myInstance(proto, right);
}
}

快排

1、通过下表取排序区间的第0个数为基数

2、排序区间基数以后,从右往左,寻找比基数小的,从左往右,寻找比基数大的,原地交换;

3、重复步骤2直到 i >= j;

4、将基数与下标为 i 的元素原地交换,从而实现划分;

5、递归排序基数左边的数,递归排序基数右边的数,返回数组。

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
// quickSort(arr, 0, arr.length-1)
var quickSort = function(ary, left, right) {
if(left >= right) {
return ary;
}

let i = left,
j = right,
base = ary[left];

while (i < j) {
// 从右边起,寻找比基数小的数
while (i<j && ary[j] >= base) {
j--;
}

// 从左边起,寻找比基数大的数
while (i<j && ary[i] <= base) {
i++
}

if (i<j) {
var temp = ary[i];
ary[i] = ary[j];
ary[j] = temp;
}
}

ary[left] = ary[i];
ary[i] = base;

quickSort(ary, left, i-1);
quickSort(ary, i+1, right);

return ary;
}

两个有序数组的合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function sort1(a,b){
var i=0,j=0,k=0;
var result=[]
while(i<a.length&&j<b.length){
if(a[i]<b[j]){
result[k++]=a[i++];
}else{
result[k++]=b[j++];
}
}
while(i<a.length){
result[k++]=a[i++];
}
while(j<b.length){
result[k++]=b[j++]
}
return result
};

爬楼梯

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
// 递归
function Fibonacci(n)
{
if (n==0 || n==1) return n;
return Fibonacci(n-1) + Fibonacci(n-2);
}


// 动态规划
function Fibonacci(n)
{
// write code here
let qian = 0,
hou = 1;

if(n === 0) return 0;
if(n === 1) return 1;

for(let i = 2;i<=n; ++i){
let x = qian +hou;
qian = hou;
hou = x;
}
return hou;
}

判断单链表是否有环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function judge(list) {
//快慢指针
let fast = list.next.next,
slow = list.next;
//当快指针能够走到头表示无环
while (list) {

if (fast == slow) {
return true;
}
fast = fast.next.next;//走两步
slow = slow.next;//走一步
}
return false;
}

有效括号

首先,我们创建一个数组,以存放与左括号配对的右括号。我们遍历括号字符串,判断该符号是否为左括号,如果是,就将其相应的右括号压入(push)结果数组中;如果不是,就将结果数组的值弹出(pop),看弹出的值是否和符号位相同,如果不同,就代表未正确配对。当括号字符串遍历完之后,我们根据结果数组是否为空就能确保代码运行正确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var isValid = function(s) {
var rightSymbols = [];
for (var i = 0; i < s.length; i++) {
if(s[i] == "("){
rightSymbols.push(")");
}else if(s[i] == "{"){
rightSymbols.push("}");
}else if(s[i] == "["){
rightSymbols.push("]");
}else if(rightSymbols.pop() != s[i] ){
return false;
}
}
return !rightSymbols.length;
};

LRU(最近最少使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class LRUCache {
constructor(capacity) {
this.capacity = capacity
this.map = new Map()
}

get(key) {
let val = this.map.get(key)
if (typeof val === 'undefined') { return -1 }
this.map.delete(key)
this.map.set(key, val)
return val
}

put(key, value) {
if (this.map.has(key)) {
this.map.delete(key)
}

this.map.set(key, value)
let keys = this.map.keys()
while (this.map.size > this.capacity) { this.map.delete(keys.next().value) }
}
}

两个二叉树是否相等

1
2
3
4
5
6
7
8
9
10
11
//严格比较二叉树,左子树和右子树必须完全一样,不可互换
function compareTree(root1, root2){
if(root1 == root2) return true;//两棵树是同一棵树
if((root1 == null && root2 != null) || (root1 != null && root2 == null)) return false;//两棵树有一棵为null,另一棵不是null
if(root1.value != root2.value) return false;//节点的值不同
var leftBoolean = compareTree(root1.left, root2.left);//比较左子树
var rightBoolean = compareTree(root1.right, root2.right);//比较右子树

return leftBoolean && rightBoolean;//左子树和右子树必须都一样
}

二叉树的前序、中序、后序遍历

前序(根左右),中序(左根右),后序(左右根)。根据的位置来的

重建的时候根据根位置找到树的左子树和右子树,然后让左右子树继续根据根节点位置找的新的左右子树

后序dfs

1
2
3
4
5
// 深度遍历
var maxDepth = function(root) {
if(!root) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}

array.slice()留头不留尾

两个栈实现队列操作

入队操作

  • 正常push就好

出队操作

  1. 将栈1的元素挪到栈2中
  2. 把栈2顶端的数据出栈即可
  3. 随后将栈2里的数据再挪到栈1里面(还原数据以方便后续入队)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let arr1 = []
let arr2 = []

function push(node)
{
arr1.push(node)
}
function pop()
{
if (!arr2.length) {
while(arr1.length) {
arr2.push(arr1.pop())
}
}
return arr2.pop()
}

拖拽实现

实现思路:

  1. 鼠标按下开始拖拽
  2. 记录摁下鼠标时的鼠标位置以及元素位置
  3. 拖动鼠标记下当前鼠标的位置
  4. 鼠标当前位置-摁下时鼠标位置= 鼠标移动距离
  5. 元素位置= 鼠标移动距离+鼠标摁下时元素的位置
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
class Drag {
//构造函数
constructor(el) {
this.el = el;
//鼠标摁下时的元素位置
this.startOffset = {};
//鼠标摁下时的鼠标位置
this.startPoint = {};
let move = (e) => {
this.move(e);
};
let end = (e) => {
document.removeEventListener("mousemove", move);
document.removeEventListener("mouseup", end);
};
el.addEventListener("mousedown", (e) => {
this.start(e);
document.addEventListener("mousemove", move);
document.addEventListener("mouseup", end);
})
}
//摁下时的处理函数
start(e) {
let { el } = this;
this.startOffset = {
x: el.offsetLeft,
y: el.offsetTop
}
this.startPoint = {
x: e.clientX,
y: e.clientY
}
}
//鼠标移动时的处理函数
move(e) {
let { el, startOffset, startPoint } = this;
let newPoint = {
x: e.clientX,
y: e.clientY
}
let dis = {
x: newPoint.x - startPoint.x,
y: newPoint.y - startPoint.y,
}
el.style.left = dis.x + startOffset.x + "px";
el.style.top = dis.y + startOffset.y + "px";
}
}

(function () {
let box = document.querySelector("#box");
let dragbox = new Drag(box);
})()

手写订阅发布

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
class Subject {
observers = []

addObserver(observer) {
this.observers.push(observer)
}
removeObserver(observer) {
let index = this.observers.indexOf(observer)
if(index > -1){
this.observers.splice(index, 1)
}
}
notify() {
this.observers.forEach(observer => {
observer.update()
})
}
}


class Observer{
update() {}
subscribeTo(subject) {
subject.addObserver(this)
}
}

统计次数最多的三个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const allTag = [...document.getElementsByTagName('*')];
console.log('所有的标签', allTag);
const allTagMap = allTag.map((item, index)=>{
return (item.tagName)
});
//将标签的节点格式化
var obj = {};
allTagMap.map((item, index)=>{
if(item in obj){
obj[item]+=1;
}else{
obj[item]=1;
}
});
function sortTag(obj){
return Object.keys(obj).sort((a,b)=>obj[b]-obj[a]); // 按照其属性值进行排列
}
var resultSortTag = sortTag(obj);
var theFirstsThree = {};
for(var i = 0; i<3; i++){
var qian = resultSortTag[i];
theFirstsThree[qian] = obj[qian];
}
console.log('前三个标签是:', theFirstsThree);

柯里化函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function add(num){
let sum=0;
sum = sum+num;
let tempFun=function(numB){
if(arguments.length===0){
return sum;
}else{
sum= sum+ numB;
return tempFun;
}
}

return tempFun;
}

var result=add(2)(3)(4)(5)();

全排列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var a = [1, 2, 3];
//swap用来交换数组a中的两个元素
function swap(a, p, q) {
var t = a[p];
a[p] = a[q];
a[q] = t;
}

//全排列函数pai,在数组a中,对p位置到q位置之间的元素进行全排列
function pai(a, p, q) {
if (p == q) { console.log(a); }//一个数的全排列就是自己,输出自己
else {
for (var i = p; i < q; i++) {
swap(a, i, p);//把 a 中的每个元素都作一次头元素
pai(a, p + 1, q);//对头元素后的数据再次递归实现全排列
swap(a, i, p);//排完之后要换回来,防止重复排列
}
}
}

pai(a, 0, a.length);