# 手写题
# 手写call
Function.prototype.myCall=function(ctx,...args){
//1、将方法挂载到我们传入的ctx
//2、将挂载以后的方法调用
//3、将我们添加的这个属性删除
ctx.fn=this
ctx.fn(...args)
delete ctx.fn
}
//测试
function people(...args){
console.log(args)
console.log(this.name)
}
people.myCall({name:'lucy'},'father','mother')
详细版
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
}
# 手写apply
Function.prototype.myApply=function(ctx,args=[]){
//1、将方法挂载到我们传入的ctx
//2、将挂载以后的方法调用
//3、将我们添加的这个属性删除
if(args && !(args instanceof Array)){
throw('myApply 只接受数组作为参数')
}
ctx.fn=this
ctx.fn(...args)
delete ctx.fn
}
//测试
function people(...args){
console.log(args)
console.log(this.name)
}
people.myCall({name:'lucy'},['father','mother'])
详细版
Function.prototype.myApply=function(context){
//判断调用对象是否为函数
if(typeof this!=="function"){
throw new TypeError("Error")
}
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
}
# 手写bind
Function.prototype.bind = function(obj){
var arg1 = [].slice.call(arguments,1); //用arg1 保留了当函数调用bind方法时候传入的参数,因为arguments是类数组对象,我们借用了数组的slice方法
var fn = this; // fun —> bind调用者(也就是某个函数)
return function(){
fn.apply(obj,arg1.concat([].slice.call(arguments)));
}
}
//测试样例
function fun(arg1, arg2) {
console.log(this.name)
console.log(arg1 + arg2)
}
const _this = { name: 'YIYING' }
const newFun = fun.abind(_this)
newFun(1, 2)
//输出:
YIYING
3
# 手写new
new做了什么
- 创建一个新的对象
- 继承父类原型上的方法
- 添加父类的属性到新的对象上并初始化. 保存方法的执行结果
- 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
function _new(obj, ...rest){
// 基于obj的原型创建一个新的对象
const newObj = Object.create(obj.prototype);
// 添加属性到新创建的newObj上, 并获取obj函数执行的结果.
const result = obj.apply(newObj, rest);
// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
return typeof result === 'object' ? result : newObj;
}
# 手写函数防抖和函数节流⭐
节流(高频触发的事件,在单位时间,只响应第一次)
function throttle(fn, delay){
let canUse = true
return function(){
if(canUse){
fn.apply(this, arguments)
canUse = false
setTimeout(()=>canUse = true, delay)
}
}
}
防抖(高频触发的事件,在单位时间,只响应最后一次,如果在指定时间再次触发,则重新计算时间)
function debounce(fn, delay){
let timer = null
return function(){
if(timer){window.clearTimeout(timer)}
timer = setTimeout(()=>{
fn.apply(this, arguments)
timer = null
},delay)
}
}
const debounced = debounce(()=>console.log('hi'))
debounced()
debounced()
# 手写AJAX⭐
var request = new XMLHttpRequest()
request.open('GET', '/a/b/c?name=ff', true);
request.onreadystatechange = function () {
if(request.readyState === 4 && request.status === 200) {
console.log(request.responseText);
}};
request.send();
第1步:创建XMLHttpRequest对象,也就是创建一个异步调用对象 第2步:创建一个新的HTTP请求,并指定该HTTP请求的方法、URL以及验证信息。 第3步:设置响应HTTP状态变化的函数。 第4步:发送HTTP请求。
补充:
- 0:请求未初始化(还没有调用
open()
)。 - 1:请求已经建立,但是还没有发送(还没有调用
send()
)。 - 2:请求已发送,正在处理中(通常现在可以从响应中获取内容头)。
- 3:请求在处理中;通常响应中已有部分数据可用了,但是服务器还没有完成响应的生成。
- 4:响应已完成;您可以获取并使用服务器的响应了。
jQuery实现jsonp------不是真正的ajax,没有用到xhr
$.ajax({
url:'http://api.json',
dataType:'jsonp',
jsonpCallback:'callback',
success:function(data){
console.log(data)
}
})
# Ajax请求:POST
// 异步对象
var xhr = new XMLHttpRequest();
// 设置属性
xhr.open('post', '02.post.php');
// 如果想要使用post提交数据,必须添加此行
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// 将数据通过send方法传递
xhr.send('name=fox&age=18');
// 发送并接受返回值
xhr.onreadystatechange = function () {
// 这步为判断服务器是否正确响应
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
ajax中post请求头的几种类型
- application/json(JSON数据格式)
这种类型是我们现在最常用的,越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。
- application/x-www-form-urlencoded
这应该是最常见的 POST 提交数据的方式了。浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据
- multipart/form-data
- text/xml
# 如何实现浅拷贝,深拷贝?⭐
https://juejin.im/post/6844904205937803277
浅拷贝
复制一份,不会引起连锁改变
- 方法一:
Object.assign
实现
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
- 方法二:通过展开运算符
...
来实现浅拷贝
let a = {
age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1
深拷贝
背代码,要点:https://juejin.im/post/5ece6dfbe51d4578a6796fd8
https://segmentfault.com/a/1190000020255831
- 递归
- 判断类型
- 检查环(也叫循环引用)
- 需要忽略原型
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {// 保证 key 不是原型的属性
result[key] = deepClone(obj[key])// 递归调用!!!
}
}
// 返回结果
return result
}
简易版:JSON.parse(JSON.stringify(obj))
# JS继承的实现方式⭐
https://www.cnblogs.com/humin/p/4556820.html
定义一个父类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
原型链继承
父类的实例作为子类的原型
function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
原型链的问题 a.对象实例共享所有继承的属性和方法 b.创建子类型的实例的时候,不能传递参数
构造函数继承(伪造对象、经典继承)
复制父类的实例属性给子类
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
子类构造函数向父类构造函数中传递参数 可以实现多继承
不能继承原型属性/方法,只能继承父类的实例属性和方法
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
实例继承(原型式继承)
function Cat(name){
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false
实例是父类的实例,不是子类的实例
不支持多继承
**组合式继承 (构造+原型链) **
调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
既可以继承实例属性/方法,也可以继承原型属性/方法
可传参,可函数复用
寄生组合继承
ES6继承----完全可以看作构造函数的另一种写法
Class 可以通过extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。
如果不调用super
方法,子类就得不到this
对象。
class Animal{
constructor(color){
this.color = color
}
move(){}
}
class Dog extends Animal{
constructor(color,name){
super(color) //调用父类的constructor(color)
this.name=name
}
}
ES5继承和ES6继承的区别
ES5 的继承,实质是先创造子类的实例对象this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this
上面(所以必须先调用super
方法),然后再用子类的构造函数修改this
。
es5继承首先是在子类中创建自己的this指向,最后将方法添加到this中
Child.prototype=new Parent() || Parent.apply(this) || Parent.call(this)
es6继承是使用关键字先创建父类的实例对象this,最后在子类class中修改this
阮一峰:
https://es6.ruanyifeng.com/#docs/class-extends
# JS实现邮箱的正则表达式
var pattern = /^([A-Za-z0-9_\-\.])+@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
console.log(pattern.test('12343qqbc@163.dm'))
# 数组扁平化
将一个嵌套多层的数组array(嵌套可以是任何层数)转换为只有一层的数组
flatten([1,[2,[3,4]]]) //[1,2,3,4] ?想不通运行不起来
arr.flat(2) //[1,2,3,4]
实现一个flatten
function flatten(arr) { return arr.toString().split(',')}
# 如何实现数组去重?⭐
https://www.cnblogs.com/wisewrong/p/9642264.html
1、哈希
2、双重 for 循环(最烂)
3、for...of + indexOf()或includes() ,创建一个空数组,元素不存在时push进去
4、Array.sort() 比较相邻元素是否相等,从而排除重复项
5、new Set([iterable])
# Promise、Promise.all、Promise.race 怎么用
- 背代码 Promise 用法
function fn(){
return new Promise((resolve, reject)=>{
成功时调用 resolve(数据)
失败时调用 reject(错误)
})
}
fn().then(success, fail).then(success2, fail2)
//更直观的写法
promise.then(function(value) {
// success
}, function(value) {
// failure
});
- 背代码 Promise.all 用法
promise1和promise2都成功才会调用success1
Promise.all([promise1, promise2]).then(success1, fail1)
背代码 Promise.race 用法
promise1和promise2只要有一个成功就会调用success1; promise1和promise2只要有一个失败就会调用fail1; 总之,谁第一个成功或失败,就认为是race的成功或失败。
Promise.race([promise1, promise2]).then(success1, fail1)
# 手写promise all
Promise.all_ = function(arr) {
return new Promise((resolve, reject) => {
// Array.from()可将可迭代对象转化为数组
arr = Array.from(arr);
if(arr.length===0) {
resolve([]);
} else {
let result = [];
let index = 0;
for(let i=0; i<arr.length; i++) {
// 考虑到arr[i]可能是thenable对象也可能是普通值
Promise.resolve(arr[i]).then(data => {
result[i] = data;
if(++index===arr.length) {
// 所有的promise状态都是fulfilled,promise.all返回的实例才变成fulfilled状态
resolve(result);
}
}, err => {
reject(err);
return;
})
}
}
})
}
# 手写promise race
Promise.race_= function(arr) {
arr = Array.from(arr);
return new Promise((resolve, reject) => {
if(arr.length===0) {
return;
} else {
for(let i=0; i<arr.length; i++) {
Promise.resolve(arr[i]).then(data => {
resolve(data);
return;
}, err => {
reject(err);
return;
})
}
}
})
}
# 模拟Object.create实现
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象__proto__------原型链继承
function simulateCreate (proto) {
var F = function () {};
F.prototype = proto;
return new F();
}
# ES5下实现const
关键在于object.defineProperty()这个API。可以接受三个参数,
object.defineProperty(obj,prop,desc);
obj:在其上面定义属性的对象
prop:要定义和修改的属性
desc:将被定义或修改的属性描述符,其中有
value 该属性对应的值,默认undefined
get 提供getter方法
set 提供set方法
writable 当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false
enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举 默认为 false
Configurable 表示对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改 默认为 false
function _const(key, value) {
let desc = {
value,
writable: false
}
Object.defineProperty(window, key, desc)
}
_const('obj', {i: 1}) //定义obj
obj.b = 3 //可以正常给obj的属性赋值
obj = {} //抛出错误,提示对象read-only
# 实现点击ul弹出li的序号和内容
<body>
<ul id = 'ul'>
<li>第1个li</li>
<li>第2个li</li>
<li>第3个li</li>
<li>第4个li</li>
<li>第5个li</li>
</ul>
</body>
var Ul=document.getElementById('ul')
var Li=Ul.getElementsByTagName('li')
for(let i=0;i<Li.length;i++){
Li[i].onclick=function(){
alert(i+Li[i].innerText)
}
}
方法二事件代理
var ulItem = document.querySelector("ul");
ulItem.onclick = function (e) {
var target = e.target
if (target.tagName.toLowerCase() === "li") {
var li = this.querySelectorAll("li");
index = Array.prototype.indexOf.call(li, target);
alert(target.innerHTML + index);
}
}
# JS对象转URL
convertObj(data) {
var _result = [];
for (var key in data) {
var value = data[key];
if (value.constructor == Array) {
value.forEach(function(_value) {
_result.push(key + "=" + _value);
});
} else {
_result.push(key + '=' + value);
}
}
return _result.join('&');
},
console.log(convertObj(query))
//news_id=144&scorce=seo
# 实现点击删除链接后,删除当前点击所在的li项。
<ul class=”list”>
<li>aa<a href="/">删除</a></li>
<li>bb<a href="/">删除</a></li>
<li>cc<a href="/">删除</a></li>
</ul>
示例,(答题要点:查找元素,监听事件,阻止默认操作、删除节点)
var list = document.querySelector('.list');
list.addEventListener('click',function(event){
event.preventDefault();
if (event.target.tagName === 'A'){
var li = event.target.parentNode;
list.removeChild(li);
}
})
# 实现一个圆形左右无限循环来回移动
div{
width:40px;
height:40px;
background:red;
border-radius:50%;
animation:move 2s ease infinite;
position:absolute;
}
@keyframes move{
0%{left:0px}
50%{left:100px}
100%{left:0px}
}
# 用于从cookie里面获取数据
function deal(){
var cookie = document.cookie;
var arr = cookie.split(';');
var obj = {};
arr.forEach(function(item){
var itemArr = item.split('=');
obj[itemArr[0]] = itemArr[1];
})
return obj;
}
# 编写一个Toast 组件,要求:从底部渐变上移到屏幕正中间,2秒后消失。
<body>
<div class='toast'>弹窗组件</idv>
</body>
<script>
var toast=document.getElementsByClassName('toast')[0]
var parent=toast.parentNode
setTimeout(()=>{
parent.removeChild(toast)
},2000)
</script>
.toast{
width:200px;
height:80px;
border:1px black solid;
text-align:center;
line-height:80px;
position:fixed;
left:50%;
top:50%;
margin-top:-40px;
margin-left:-100px;
animation:move 1s linear;
}
@keyframes move{
0%{
transform:translateY(100%);
opacity:0;
}
100%{
transform:translateY(0);
opacity:1;
}
}
# 实现一个轮播图
Swiper使用方法
https://swiper.com.cn/usage/index.html
大致结构
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide">Slide 1</div>
<div class="swiper-slide">Slide 2</div>
<div class="swiper-slide">Slide 3</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
<!-- 如果需要导航按钮 -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
<script>
var mySwiper = new Swiper ('.swiper-container', {
// direction: 'vertical', 垂直切换选项
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
})
</script>
# 实现一个parseUrl函数
function parseUrl(str){
let arr=str.split('?')
let arr2=arr[1].split('#')
let hashitem=arr2[1]
let keylist=arr2[0].split('&')
// console.log(keylist)
return {
path:arr[0],
param:(()=>{
var ret={}
for(let item of keylist){
let arritem=item.split('=')
ret[arritem[0]]=arritem[1]
}
// console.log(ret)
return ret
})(),
hash:hashitem
}
}
console.log(parseUrl("http://www.xiyanghui.com/product/list?id=123456&sort=discount#title"))
输出结果
{
path: 'http://www.xiyanghui.com/product/list',
param: { id: '123456', sort: 'discount' },
hash: 'title'
}
# 数组结构转树结构
/**
* 将一维的扁平数组转换为多层级对象
* @param {[type]} list 一维数组,数组中每一个元素需包含id和parent_id两个属性
* @return {[type]} tree 多层级树状结构
*/
function buildTree(list){
let temp = {};
let tree = {};
for(let i in list){
temp[list[i].id] = list[i];
}
for(let i in temp){
if(temp[i].parent_id) {
if(!temp[temp[i].parent_id].children) {
temp[temp[i].parent_id].children = new Object();
}
temp[temp[i].parent_id].children[temp[i].id] = temp[i];
} else {
tree[temp[i].id] = temp[i];
}
}
return tree;
}
https://blog.csdn.net/qq_37746973/article/details/78662177?utm_medium=distribute.pc_relevant.none-task-blog-blogcommendfrommachinelearnpai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-blogcommendfrommachinelearnpai2-1.channel_param
# getElementsByClassName兼容性问题
if(!document.getElementsByClassName){ //能进来一定是低版本ie(ie8及以下)
document.getElementsByClassName=function(eleclass){
var Ele=document.getElementsByTagName("*"), //通配符,获得所有的标签
reg=new RegExp("\\b"+eleclass+"\\b"),
arr=[];
for(var i=0;i<Ele.length;i++){
if(reg.test(Ele[i].className)){ //为true表示当前元素符合条件,放进数组
arr.push(Ele[i]);
}
}
return arr;
};
}
# reduce累加数组对象中值
let array = [
{
name: 'apple',
price: 10
}, {
name: 'banana',
price: 9
}
];
let sum=array.reduce((total,cur)=>{
return total+cur.price
})
console.log(sum)
# 根据str输出dom对象
function dealStr(s) {
var div = document.createElement('div')
div.innerHTML = s
return div.children[0]
}
function createVistualDom(dom) {
if (!dom) return
let obj = {}
obj.type = dom.tagName.toLowerCase()
obj.children = []
if (dom.children) {
for(let i = 0 ; i < dom.children.length ; i++) {
const child = dom.children[i]
obj.children.push(createVistualDom(child))
}
}
return obj
}
const dom = dealStr(template)
const result = createVistualDom(dom)
# Promise请求失败重连(小米面试)
function fetchWithRetry(url, options, retryTimes) {
return new Promise((resolve, reject) => {
return fetch(url, options)
.then((res) => resolve(res))
.catch((error) => {
if (retryTimes === 0) {
return reject(error);
}
return fetchWithRetry(url, options, retryTimes - 1);
});
});
}