new
操作符
function myNew(Func, ...args) {
// 1. 创建一个新对象
const obj = {}
// 2. 新对象原型指向构造函数原型对象
obj.__proto__ = Func.prototype
// 3. 将构造函数的 this 指向新对象
let result = Func.apply(obj, args)
// 4. 根据返回值判断
// 如果该函数没有返回对象,则返回this
return result instanceof Object ? result : obj
}
实现 call()
方法
Function.prototype.myCall = function (context, ...args) {
let cxt = context || window;
//将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this)
//新建一个唯一的Symbol变量避免重复
let func = Symbol()
cxt[func] = this;
args = args ? args : []
//以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向
const res = args.length > 0 ? cxt[func](...args) : cxt[func]();
//删除该方法,不然会对传入对象造成污染(添加该方法)
delete cxt[func];
return res;
}
实现 apply()
方法
Function.prototyp.myApply = function (context, args = []) {
let ctx = context || window
let func = Symbol()
ctx.func = this
const res = args.length > 0 ? ctx.func(...args) : ctx.func()
delete ctx.func
return res
}
实现 bind()
方法
Function.prototype.bind = function (context) {
// 调用 bind 的不是函数,需要抛出异常
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
// this 指向调用者
var self = this;
// 实现第2点,因为第1个参数是指定的this,所以只截取第1个之后的参数
var args = Array.prototype.slice.call(arguments, 1);
// 实现第3点,返回一个函数
return function () {
// 实现第4点,这时的arguments是指bind返回的函数传入的参数
// 即 return function 的参数
var bindArgs = Array.prototype.slice.call(arguments);
// 实现第1点,改变返回函数的this指向
return self.apply(context, args.concat(bindArgs));
}
}
但还有一个问题,bind 有以下一个特性:
一个绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
寄生式组合继承
借用父构造函数继承父类属性、结果用原型对象继承父类型方法
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function () {
console.log(100000);
};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father(); // Son.prototype = Object.create(Father.prototype)
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
ES6继承
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
eat() {
console.log(this.name + 'eat food!')
}
}
class Woman extends Person {
constructor(name, age, sex) {
super(name, age)
this.sex = sex
}
eat() {
super.eat()
}
}
instanceof的实现
instanceof 运算符用来检测 constructor.prototype是否存在于参数 object 的原型链上。
function myInstanceof(a, b) {
let right = b.prototype
let left = a.__proto__
while (true) {
if (left == null) {
return false
}
if (left == right) {
return true
}
left = left.__proto__
}
}
// 上面方法的__proto__可能不兼容
function myInstanceof (left, right) {
// 获取对象原型
let proto = Object.getPrototypeOf(left)
// 获取构造函数的prototype对象
let prototype = right.prototype
// 判断构造函数的prototype对象是否在对象的原型链上
while (true) {
if (!proto) return false
if (proto === prototype) return true
proto = Object.getPrototypeOf(proto)
}
}
Object.create的实现
function myCreate(obj) {
function F() {}
F.prototype = obj
return new F()
}
Object.assign的实现
// 剩余参数语法允许我们将一个不定数量的参数表示为一个数组
Object.myAssign = function (target, ...source) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object')
}
let ret = Object(target)
source.forEach(function (obj) {
if (obj !== null) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
ret[key] = obj[key]
}
}
}
})
return ret
}
Promise
Promise.all()
的实现
Promise.all = function(promiseArr) {
let result = [], index = 0
return new Promise((resolve, reject) => {
promiseArr.forEach((p,i) => {
Promise.resolve(p).then(val => {
index++
result[i] = val
if (index === promiseArr.length) {
resolve(result)
}
}, err => {
reject(err)
})
})
})
}
Promise.race()
的实现
Promise.race = function(promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach((p) => {
Promise.resolve(p).then(val => {
resolve(val)
}, err => {
reject(err)
})
})
})
}
Ajax的实现
// 存储的是默认值
var defaults = {
type: 'get',
url: '',
data: {},
header: {
'Content-Type: application/x-www-form-urlencoded'
}
success: function() {}
error: function() {}
}
function ajax (url, method, body, headers) {
returan new Promise(resolve, reject) {
let xhr = new XMLHttpRequest()
xhr.open(method, url)
// 设置请求头
for(let key in headers) {
xhr.setRequestHeader(key, headers[key])
}
xhr.send(body)
xhr.onreadystatechange(() => {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(xhr.responseText)
} else {
reject(xhr.status)
}
})
}
}
防抖(debounce)
对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如下面的1000毫秒)内,事件处理函数只执行一次。
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
function debounce(fn, delay) {
let timer = null
// debounce 被调用的时候,this 此时指向 window
console.log('debounce 里面的 this 指向' + this)
return function () {
// 匿名函数被调用的时候,this 指向 HTMLElement(此时指向document)
console.log('返回的匿名函数里面的 this 指向' + this)
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(fn, delay)
}
}
function test() {
// test被调用的时候,this 此时指向 window,应证了setTimeout 里面的回调函数的this指向window
console.log('debounce', this);
}
// debounce 一开始就被调用了,并非是由 onmousemove 事件触发的,不要混淆了。我就混淆了,分析了好久,一天就没了呜呜~
document.onmousemove = debounce(test, 3000)
节流(throttle)
我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。实现 这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态:
function throttle(fn, delay) {
let valid = true
return function () {
if (!valid) {
return false
}
valid = false
setTimeout(() => {
fn()
valid = true
}, delay)
}
}
function test() {
// test被调用的时候,this 此时指向 window,应证了setTimeout 里面的回调函数的this指向window
console.log('throttle', this);
}
// throttle 一开始就被调用了,并非是由 onmousemove 事件触发的,不要混淆了。我就混淆了,分析了好久,一天就没了呜呜~
document.onmousemove = throttle(test, 3000)
深拷贝(deepclone)
const target = {
field1: 1,
field2: undefined,
field3: 'ConardLi',
field4: {
child: 'child',
child2: {
child2: 'child2'
}
}
};
target.target = target
function deepClone (obj, map = new WeakMap()) {
if(obj instanceof RegExp) return new RegExp(obj)
if(obj instanceof Date) return new Date(obj)
if(obj === null || typeof obj !== 'object') return obj
if(map.has(obj)) {
return map.get(obj)
}
let cloneObj = Array.isArray(obj) ? [] : {}
map.set(obj, cloneObj)
for (let key in obj) {
if(obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key],map)
}
}
return cloneObj
}
const cloneTarget = deepClone(target)
console.log(cloneTarget)
数组扁平化(flat)
let arr = [1, 2, [3, 4, [5, [6]]]]
function fn(arr) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? fn(cur) : cur)
}, [])
}
// [1, 2, 3, 4, 5, 6]
console.log(fn(arr))
function fn() {
let ret = []
return function flat(arr) {
for(let item of arr) {
if(item.constructor === Array) {
ret.concat(flat(item))
} else {
ret.push(item)
}
}
return ret
}
}
const arr = [1, 2, 3, [4, 5], [6, [7, [8]]]]
console.log(fn()(arr));
function flat(arr) {
var res = [];
for (let item of arr) {
if (Array.isArray(item)) {
res = res.concat(flat(item));
} else {
res.push(item);
}
}
return res
}
console.log(flat([1,2,[3,4],5,[6,[7,8]],9]));
函数柯里化
我们已经知道,在函数主体中,arguments数组的length属性指定了传递给该函数的实参数目。但是函数自身的length属性的含义却并非如此,它是只读特性,返回的是函数需要的实参的数目,也就是在函数的形参列表中声明的形参的数目。调用函数时可以传递给它任意数目的实参,函数能够从arguments数组中得到这些参数,而无需考虑它所声明的形参的数目。Function对象的length属性确切说明了一个函数声明的形参的个数。注意和arguments.length不同,这个length属性在函数体的内部和外部都有效。
function curry(func) {
// func.length === 3 表示函数的形参列表中声明的形参的数目
return function curried(...args) {
if (args.length >= func.length) {
// 下面的func.apply()里面的this指向window
return func.apply(this, args);
} else {
return function (...args2) {
// 下面直接使用curried(args.concat(args2))好像也没什么问题
return curried.apply(this, args.concat(args2));
}
}
};
}
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
console.log(curriedSum(1, 2, 3));
实现sleep()
写法一
function sleep (time) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('shuchu'), time)
})
}
sleep(3000).then(value => {
console.log(value)
console.log(3333);
})
写法二
function sleep (delay) {
let start = (new Date()).getTime()
while ((new Date()).getTime() - start < delay) {
continue
}
}
function test () {
console.log('111');
sleep(2000);
console.log('222');
}
test()
图片懒加载
let num = document.getElementsByTagName('img').length
let img = document.getElementsByTagName('img')
let n = 0
lazyload()
window.onscroll = lazyload
function lazyload() {
let seeHeight = document.documentElement.clientHeight
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
for(let i = n; i < num; i++) {
if(img[i].offsetTop - scrollTop < seeHeight) {
if(img[i].getAttribute('src') == './img2/timg.gif') {
img[i].src = img[i].getAttribute('data-src')
}
n = i+1
}
}
}
在懒加载的实现中,有两个关键的数值:一个是当前可视区域的高度,另一个是元素距离可视区域顶部的高度。当前可视区域的高度,在现代浏览器及 IE9 以上的浏览器中,可以使用window.innerHeight属性获取,在低版本的 IE 中使用document.documentElment.clientHeight 获取,这里我们兼容两种情况:
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
而元素距离可视区域顶部的高度,这里我们用 getBoundingClientRect()方法来获取返回元素的大小和相对于尺寸的位置,对于该 API,MDN 的解释是:
Element.getBoundingClientRect()
方法返回元素的大小及其相对于视口的位置。
返回的属性中有一个相对于可视区域顶部的高度也就是top属性,刚好就是我们需要的元素距离顶部的距离。
let imgList = [...document.querySelectorAll('img')]
let length = imgList.length
const imgLazyLoad = function () {
let count = 0
return (function () {
console.log(length);
let deleteIndexList = []
imgList.forEach((img, index) => {
// Element.getBoundingClientRect()返回元素的大小及其相对于视口的位置
let rect = img.getBoundingClientRect()
// 若图片进入了可视区
if (rect.top < window.innerHeight) {
img.src = img.dataset.src
deleteIndexList.push(index)
count++
}
if (count === length) {
document.removeEventListener('scroll', imageLazyLoad)
}
})
imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
console.log(imgList);
})()
}
document.addEventListener('scroll', imgLazyLoad)
URL参数解析
function parseParam(url,paramkey) {
// const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
const paramsArr = url.split('?')[1].split('#')[0].split('&'); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 处理有 value 的参数
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else { // 处理没有 value 的参数
paramsObj[param] = true;
}
})
// return paramsObj.hasOwnProperty(paramkey) ? paramsObj[paramkey] : false;
return paramsObj
}
let res = parseParam('https://m.xiaohongshu.com?name=1&age=18#test1=xiaoming1', 'age')
console.log(res);
冒泡排序
function mSort(arr) {
let n = arr.length
for(let i = 0; i < n-1; i++) {
for(let j = 0; j < n-1-i; j++) {
if(arr[j] > arr[j+1]) {
let temp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
}
}
}
return arr
}
let arr = [7,6,4,5,3,1,2]
console.log(arr);
console.log(mSort(arr));
选择排序
function sSort(arr) {
let n = arr.length
for(let i = 0; i < n; i++) {
let min = i
for(let j = i + 1; j < n; j++) {
if(arr[j] < arr[min]) {
min = j
}
}
let temp = arr[i]
arr[i] = arr[min]
arr[min] = temp
}
return arr
}
let arr = [7,6,4,5,3,1,2]
console.log(arr);
console.log(sSort(arr));
快速排序
// 交换数组中子数组的记录,使枢轴记录到位,并返回其所在位置
// 此时在它之前(后)的记录均不大(小)于它
function Partition(nums, low, high) {
// 用子数组的第一个记录作为枢轴记录
let pivotkey = nums[low]
// 从数组的两端交替向中间扫描
while(low < high) {
while(low < high && pivotkey <= nums[high]) {
high--
}
// 将比枢轴记录小的记录交换到低端
let temp = nums[high]
nums[high] = nums[low]
nums[low] = temp
while(low < high && nums[low] <= pivotkey) {
low++
}
// 将比枢轴记录大的记录交换到高端
temp = nums[low]
nums[low] = nums[high]
nums[high] = temp
}
// 返回枢轴所在位置
return low
}
// 对数组中的子序列[low,low+1,...,high]做快速排序
function QSort(nums, low, high) {
if(low < high) {
// 将[low,...,high]一分为二,计算出枢轴值pivot
let pivot = Partition(nums, low, high)
QSort(nums, low, pivot-1)
QSort(nums, pivot+1, high)
}
}
let arr = [7,6,4,5,3,1,2]
QSort(arr, 0, arr.length - 1)
console.log(arr);
归并排序
以下是归并排序的步骤:
将给定的列表分为两半(如果列表中的元素数为奇数,则使其大致相等)。
以相同的方式继续划分子数组,直到只剩下单个元素数组。
从单个元素数组开始,合并子数组,以便对每个合并的子数组进行排序。
重复第 3 步单元,直到最后得到一个排好序的数组。
function merge(left, right) {
const arr = []
while (left.length && right.length) {
if (left[0] < right[0]) {
arr.push(left.shift())
} else {
arr.push(right.shift())
}
}
return [...arr, ...left, ...right]
}
function mSort(arr) {
if (arr.length < 2) {
return arr
}
let half = Math.floor(arr.length / 2)
const left = arr.splice(0, half)
return merge(mSort(left), mSort(arr))
}
console.log(mSort([7, 6, 4, 5, 3, 1, 2]));
插入排序
function iSort(arr) {
var len = arr.length
var preIndex, current
for(let i = 1; i < len; i++) {
preIndex = i-1
current = arr[i]
while(preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex+1] = arr[preIndex]
preIndex--
}
arr[preIndex+1] = current
}
return arr
}
console.log(iSort([7,6,4,5,3,1,2]))
希尔排序
function shellSort(arr) {
let n = arr.length
for(let gap = Math.floor(n/2); gap > 0; gap = Math.floor(gap/2)) {
for(let i = gap; i < n; i++) {
let j = i
let current = arr[i]
while(j-gap >= 0 && current < arr[j-gap]) {
arr[j] = arr[j-gap]
j = j - gap
}
arr[j] = current
}
}
return arr
}
console.log(shellSort([7,6,4,5,3,1,2]));