1. 手写Object.create()
思路:将传入的对象作为原型
function objectCreate(obj){
function F(){}
F.prototype = obj
return new F()
}
2.手写instanceof
(1)
instanceof原理实际上就是查找目标对象的原型链
function myInstance(L, R) {//L代表instanceof左边,R代表右边
var RP = R.prototype
var LP = L.__proto__
while (true) {
if(LP == null) {
return false
}
if(LP == RP) {
return true
}
LP = LP.__proto__
}
}
console.log(myInstance({},Object));
(2)
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
实现步骤:
- 首先获取类型的原型
- 然后获得对象的原型
- 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为
null
,因为原型链最终为null
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 获取对象的原型
prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
3.手写new
new内部:
- 创建一个新对象
- 使新对象的
__proto__
指向原函数的prototype
- 改变this指向(指向新的obj)并执行该函数,执行结果保存起来作为result
- 判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回result
function myNew(fn,...args){
// 创建一个空对象
let obj = {}
// 使空对象的隐式原型指向原函数的显示原型
obj.__proto__ = fn.prototype
// this指向obj
fn.apply(obj,args)
// 返回
return obj
}
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
function myNew(fn, ...args) {
// 判断参数是否是一个函数
if (typeof fn !== "function") {
return console.error("type error");
}
// 创建一个对象,并将对象的原型绑定到构造函数的原型上
const obj = Object.create(fn.prototype);
const value = fn.apply(obj, args); // 调用构造函数,并且this绑定到obj上
// 如果构造函数有返回值,并且返回的是对象,就返回value ;否则返回obj
return value instanceof Object ? value : obj;
}
4.防抖和节流
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
// //防抖
function debounce(fn, date) {
let timer //声明接收定时器的变量
return function (...arg) { // 获取参数
timer && clearTimeout(timer) // 清空定时器
timer = setTimeout(() => { // 生成新的定时器
//因为箭头函数里的this指向上层作用域的this,所以这里可以直接用this,不需要声明其他的变量来接收
fn.apply(this, arg) // fn()
}, date)
}
}
//--------------------------------
// 节流
function debounce(fn, data) {
let timer = +new Date() // 声明初始时间
return function (...arg) { // 获取参数
let newTimer = +new Date() // 获取触发事件的时间
if (newTimer - timer >= data) { // 时间判断,是否满足条件
fn.apply(this, arg) // 调用需要执行的函数,修改this值,并且传入参数
timer = +new Date() // 重置初始时间
}
}
}
5.手写 call 函数
call 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 处理传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性。
- 返回结果。
// call函数实现
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
call和apply实现思路主要是:
- 判断是否是函数调用,若非函数调用抛异常
- 通过新对象(context)来调用函数
- 给context创建一个
fn
设置为需要调用的函数 - 结束调用完之后删除
fn
- 给context创建一个
Function.prototype.myCall = function (context) {
// 先判断调用myCall是不是一个函数
// 这里的this就是调用myCall的
if (typeof this !== 'function') {
throw new TypeError("Not a Function")
}
// 不传参数默认为window
context = context || window
// 保存this
context.fn = this
// 保存参数
let args = Array.from(arguments).slice(1) //Array.from 把伪数组对象转为数组
// 调用函数
let result = context.fn(...args)
delete context.fn
return result
}
6.手写 apply 函数
apply 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 将函数作为上下文对象的一个属性。
- 判断参数值是否传入
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性
- 返回结果
// apply 函数实现
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
Function.prototype.myApply = function (context) {
// 判断this是不是函数
if (typeof this !== "function") {
throw new TypeError("Not a Function")
}
let result
// 默认是window
context = context || window
// 保存this
context.fn = this
// 是否传参
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
7.手写 bind 函数
bind 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 保存当前函数的引用,获取其余传入参数值。
- 创建一个函数返回
- 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply
调用,其余情况都传入指定的上下文对象。
// bind 函数实现
Function.prototype.myBind = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
bind实现思路
- 判断是否是函数调用,若非函数调用抛异常
- 返回函数
- 判断函数的调用方式,是否是被new出来的
- new出来的话返回空对象,但是实例的
__proto__
指向_this
的prototype
- new出来的话返回空对象,但是实例的
- 判断函数的调用方式,是否是被new出来的
- 完成函数柯里化
Array.prototype.slice.call()
Function.prototype.myBind = function(context){
// 判断是否是一个函数
if(typeof this !== "function") {
throw new TypeError("Not a Function")
}
// 保存调用bind的函数
const _this = this
// 保存参数
const args = Array.prototype.slice.call(arguments,1)
// 返回一个函数
return function F () {
// 判断是不是new出来的
if(this instanceof F) {
// 如果是new出来的
// 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
return new _this(...args,...arguments)
}else{
// 如果不是new出来的改变this指向,且完成函数柯里化
return _this.apply(context,args.concat(...arguments))
}
}
}
8.函数柯里化的实现
函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
function curry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
9.手写AJAX请求
创建AJAX请求的步骤:
- 创建一个 XMLHttpRequest 对象。
- 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。
- 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。
- 当对象的属性和监听函数设置完成后,最后调用 send 方法来向服务器发起请求,可以传入参数作为发送的数据体。
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", SERVER_URL, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 当请求成功时
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 设置请求失败时的监听函数
xhr.onerror = function() {
console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);
var Ajax = {
get: function (url, callback) {
let xhr = XMLHttpRequest();
xhr.open("get", url, false)
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 304) {
console.log(xhr.responseText);
callback(xhr.responseText)
}
}
}
xhr.send()
},
post: function (url, data, callback) {
let xhr = new XMLHttpRequest()
// 第三个参数为是否异步执行
xhr.open('post', url, true)
// 添加http头,设置编码类型
xhr.setRequestHeader("Content-type","x-www-form-urlencoded")
xhr.onreadystatechange = function () {
if(xhr.readyState == 4) {
if(xhr.status == 200 || xhr.status == 304) {
console.log(xhr.responseText);
callback(xhr.responseText)
}
}
}
xhr.setRequestHeader('Content-type', "application/x-www-urlencoded")
xhr.send(data)
}
}
10. 使用Promise封装AJAX请求
// promise 封装实现:
function getJSON(url) {
// 创建一个 promise 对象
let promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
// 新建一个 http 请求
xhr.open("GET", url, true);
// 设置状态的监听函数
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 当请求成功或失败时,改变 promise 的状态
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
// 设置错误监听函数
xhr.onerror = function() {
reject(new Error(this.statusText));
};
// 设置响应的数据类型
xhr.responseType = "json";
// 设置请求头信息
xhr.setRequestHeader("Accept", "application/json");
// 发送 http 请求
xhr.send(null);
});
return promise;
}
11. 手写深拷贝
function fn(obj) {
// 判断数据是否是复杂类型
if (obj instanceof Object) {
//判断数据是否是数组
if (Array.isArray(obj)) {
//声明一个空数组来接收拷贝后的数据
let result = []
obj.forEach(item => {
// 需要递归深层遍历,否则复制的是地址
result.push(fn(item))
})
// 返回输出这个数组,数组拷贝完成
return result
} else {
//如果是对象,就声明一个空对象来接收拷贝后的数据
let result = {}
for (let k in obj) {
// 使用递归深层遍历
result[k] = fn(obj[k])
}
// 返回输出这个对象,对象拷贝完成
return result
}
}
// 简单数据类型则直接返回输出
return obj
}
跨域方式
组件通信方式
继承的方法
JS数组去重方法
方法一:Set()+Array.from()/[…]
unique = (array) => {
// return [...new Set(array)]
return Array.from(new Set(array));
};
方法二:Map()
unique = (array) => {
let map = new Map();
let result = [];
for (let i = 0; i < array.length; i++) {
if (!map.has(array[i])) { // 如果map中没有该key,就加入 result 中
map.set(array[i], true);
result.push(array[i]);
}
}
return result;
};
方法三:indexOf()
unique = (array) => {
let result = [];
array.forEach(item=>{
if (result.indexOf(item) == -1) {
result.push(item);
}
})
return result;
};
方法三:includes()
unique = (array) => {
let result = [];
array.forEach(item=>{
if (!result.includes(item)) {
result.push(item);
}
})
return result;
};
方法四:filter()+indexOf()
unique = (array) => {
let result = array.filter((item, index) => {
return array.indexOf(item) == index;
});
return result;
};
方法五:reduce()+includes()
unique = (array) => {
let result = array.reduce((prev, cur) => {
return prev.includes(cur) ? prev : [...prev, cur];
}, []);
return result;
};
用js递归的方式写1到100求和
方法1
function add(n) {
if (n==1) return 1;
return add(n - 1) + n;
}
let total = add(100);
方法2
function sumFunc(num, start) {
num += start;
start++;
if (start > 100) {
console.log(num);
return num;
} else {
sumFunc(num, start);
}
}
sumFunc(0, 1);
写一个能遍历对象和数组的通用forEach函数
// forEach传入两个参数,obj,要执行的函数fn
function forEach(obj, fn) {
var key;
if (obj instanceof Array) {
obj.forEach(function (item, index) {
fn(index, item);
});
} else {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
fn(key, obj[key]);
}
}
}
}
var arr = [1, 2, 3];
forEach(arr, function (index, item) {
console.log(index, item);
});
var obj = {
x: 100,
y: 200,
};
forEach(obj, function (key, val) {
console.log(key, val);
});
foreach、map与reduce的区别
foreach:让数组中的每一项做一件事
var arr=[1,2,3,4,5]
arr.foreach(function(item,index){
console.log(item);
})
map:让数组通过某种计算产生一个新的数组
var arr=[1,2,3,4,5]
arr.map(function(item,index){
return item*2;
})
reduce:让数组中的前项和后项做某种计算,并累计得到最终值。
var arr=[1,2,3,4,5];
var result=arr.reduce(function(prev,next){
return prev+next;
})
console.log(result);
总结:
map 会返回新的数组
foreach 遍历数组中每一项
reduce 适合做累计运算