# 手写题

# 手写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

  1. 递归
  2. 判断类型
  3. 检查环(也叫循环引用)
  4. 需要忽略原型
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);
        });
    });
  }