前端面试JS高频手写大全

比眉伴天荒 2022-08-28 14:43 316阅读 0赞

来自:SegmentFault ,作者:晚起的虫儿

链接:https://segmentfault.com/a/1190000038910420

点击上方 前端Q,关注公众号

回复加群,加入前端Q技术交流群

本文涵盖了前端面试常考的各种重点手写。

建议优先掌握:

  • instanceof (考察对原型链的理解)
  • new (对创建对象实例过程的理解)
  • call&apply&bind (对this指向的理解)
  • 手写promise (对异步的理解)
  • 手写原生ajax (对http请求方式的理解,重点是get和post请求)

1、手写instanceof

instanceof作用:

判断一个实例是否是其父类或者祖先类型的实例。

instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype查找失败,返回 false

  1. let myInstanceof = (target,origin) => {
  2. while(target) {
  3. if(target.__proto__===origin.prototype) {
  4. return true
  5. }
  6. target = target.__proto__
  7. }
  8. return false
  9. }
  10. let a = [1,2,3]
  11. console.log(myInstanceof(a,Array)); // true
  12. console.log(myInstanceof(a,Object)); // true

2. 实现数组的map方法

  1. Array.prototype.myMap = function(fn, thisValue) {
  2. let res = []
  3. thisValue = thisValue||[]
  4. let arr = this
  5. for(let i in arr) {
  6. res.push(fn(arr[i]))
  7. }
  8. return res
  9. }

3. reduce实现数组的map方法

  1. Array.prototype.myMap = function(fn,thisValue){
  2. var res = [];
  3. thisValue = thisValue||[];
  4. this.reduce(function(pre,cur,index,arr){
  5. return res.push(fn.call(thisValue,cur,index,arr));
  6. },[]);
  7. return res;
  8. }
  9. var arr = [2,3,1,5];
  10. arr.myMap(function(item,index,arr){
  11. console.log(item,index,arr);
  12. })

4. 手写数组的reduce方法

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终为一个值,是ES5中新增的又一个数组逐项处理方法

参数:

  • callback(一个在数组中每一项上调用的函数,接受四个函数:)
    • previousValue(上一次调用回调函数时的返回值,或者初始值)
    • currentValue(当前正在处理的数组元素)
    • currentIndex(当前正在处理的数组元素下标)
    • array(调用reduce()方法的数组)
  • initialValue(可选的初始值。作为第一次调用回调函数时传给previousValue的值)

    function reduce(arr, cb, initialValue){

    1. var num = initValue == undefined? num = arr[0]: initValue;
    2. var i = initValue == undefined? 1: 0
    3. for (i; i< arr.length; i++){
    4. num = cb(num,arr[i],i)
    5. }
    6. return num

    }

    function fn(result, currentValue, index){

    1. return result + currentValue

    }

    var arr = [2,3,4,5]
    var b = reduce(arr, fn,10)
    var c = reduce(arr, fn)
    console.log(b) // 24

5. 数组扁平化

数组扁平化就是把多维数组转化成一维数组

  1. es6提供的新方法 flat(depth)

    let a = [1,[2,3]];
    a.flat(); // [1,2,3]
    a.flat(1); //[1,2,3]

其实还有一种更简单的办法,无需知道数组的维度,直接将目标数组变成1维数组。depth的值设置为Infinity。

  1. let a = [1,[2,3,[4,[5]]]];
  2. a.flat(Infinity); // [1,2,3,4,5] a是4维数组
  1. 利用cancat

    var arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]];
    function flatten(arr) {

    1. var res = [];
    2. for (let i = 0, length = arr.length; i < length; i++) {
    3. if (Array.isArray(arr[i])) {
    4. res = res.concat(flatten(arr[i])); //concat 并不会改变原数组
    5. //res.push(...flatten(arr[i])); //扩展运算符
    6. } else {
    7. res.push(arr[i]);
    8. }
    9. }
    10. return res;

    }
    flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

6. 函数柯里化

柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数。

当柯里化函数接收到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?

有两种思路:

  1. 通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数
  2. 在调用柯里化工具函数时,手动指定所需的参数个数

将这两点结合一下,实现一个简单 curry 函数:

  1. /**
  2. * 将函数柯里化
  3. * @param fn 待柯里化的原函数
  4. * @param len 所需的参数个数,默认为原函数的形参个数
  5. */
  6. function curry(fn,len = fn.length) {
  7. return _curry.call(this,fn,len)
  8. }
  9. /**
  10. * 中转函数
  11. * @param fn 待柯里化的原函数
  12. * @param len 所需的参数个数
  13. * @param args 已接收的参数列表
  14. */
  15. function _curry(fn,len,...args) {
  16. return function (...params) {
  17. let _args = [...args,...params];
  18. if(_args.length >= len){
  19. return fn.apply(this,_args);
  20. }else{
  21. return _curry.call(this,fn,len,..._args)
  22. }
  23. }
  24. }

我们来验证一下:

  1. let _fn = curry(function(a,b,c,d,e){
  2. console.log(a,b,c,d,e)
  3. });
  4. _fn(1,2,3,4,5); // print: 1,2,3,4,5
  5. _fn(1)(2)(3,4,5); // print: 1,2,3,4,5
  6. _fn(1,2)(3,4)(5); // print: 1,2,3,4,5
  7. _fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5

我们常用的工具库 lodash 也提供了 curry 方法,并且增加了非常好玩的 placeholder 功能,通过占位符的方式来改变传入参数的顺序。

比如说,我们传入一个占位符,本次调用传递的参数略过占位符, 占位符所在的位置由下次调用的参数来填充,比如这样:

直接看一下官网的例子:

img

接下来我们来思考,如何实现占位符的功能。

对于 lodash 的 curry 函数来说,curry 函数挂载在 lodash 对象上,所以将 lodash 对象当做默认占位符来使用。

而我们的自己实现的 curry 函数,本身并没有挂载在任何对象上,所以将 curry 函数当做默认占位符

使用占位符,目的是改变参数传递的顺序,所以在 curry 函数实现中,每次需要记录是否使用了占位符,并且记录占位符所代表的参数位置。

直接上代码:

  1. /**
  2. * @param fn 待柯里化的函数
  3. * @param length 需要的参数个数,默认为函数的形参个数
  4. * @param holder 占位符,默认当前柯里化函数
  5. * @return {Function} 柯里化后的函数
  6. */
  7. function curry(fn,length = fn.length,holder = curry){
  8. return _curry.call(this,fn,length,holder,[],[])
  9. }
  10. /**
  11. * 中转函数
  12. * @param fn 柯里化的原函数
  13. * @param length 原函数需要的参数个数
  14. * @param holder 接收的占位符
  15. * @param args 已接收的参数列表
  16. * @param holders 已接收的占位符位置列表
  17. * @return {Function} 继续柯里化的函数 或 最终结果
  18. */
  19. function _curry(fn,length,holder,args,holders){
  20. return function(..._args){
  21. //将参数复制一份,避免多次操作同一函数导致参数混乱
  22. let params = args.slice();
  23. //将占位符位置列表复制一份,新增加的占位符增加至此
  24. let _holders = holders.slice();
  25. //循环入参,追加参数 或 替换占位符
  26. _args.forEach((arg,i)=>{
  27. //真实参数 之前存在占位符 将占位符替换为真实参数
  28. if (arg !== holder && holders.length) {
  29. let index = holders.shift();
  30. _holders.splice(_holders.indexOf(index),1);
  31. params[index] = arg;
  32. }
  33. //真实参数 之前不存在占位符 将参数追加到参数列表中
  34. else if(arg !== holder && !holders.length){
  35. params.push(arg);
  36. }
  37. //传入的是占位符,之前不存在占位符 记录占位符的位置
  38. else if(arg === holder && !holders.length){
  39. params.push(arg);
  40. _holders.push(params.length - 1);
  41. }
  42. //传入的是占位符,之前存在占位符 删除原占位符位置
  43. else if(arg === holder && holders.length){
  44. holders.shift();
  45. }
  46. });
  47. // params 中前 length 条记录中不包含占位符,执行函数
  48. if(params.length >= length && params.slice(0,length).every(i=>i!==holder)){
  49. return fn.apply(this,params);
  50. }else{
  51. return _curry.call(this,fn,length,holder,params,_holders)
  52. }
  53. }
  54. }

验证一下:;

  1. let fn = function(a, b, c, d, e) {
  2. console.log([a, b, c, d, e]);
  3. }
  4. let _ = {}; // 定义占位符
  5. let _fn = curry(fn,5,_); // 将函数柯里化,指定所需的参数个数,指定所需的占位符
  6. _fn(1, 2, 3, 4, 5); // print: 1,2,3,4,5
  7. _fn(_, 2, 3, 4, 5)(1); // print: 1,2,3,4,5
  8. _fn(1, _, 3, 4, 5)(2); // print: 1,2,3,4,5
  9. _fn(1, _, 3)(_, 4,_)(2)(5); // print: 1,2,3,4,5
  10. _fn(1, _, _, 4)(_, 3)(2)(5); // print: 1,2,3,4,5
  11. _fn(_, 2)(_, _, 4)(1)(3)(5); // print: 1,2,3,4,5

至此,我们已经完整实现了一个 curry 函数~~

7. 实现深拷贝

浅拷贝和深拷贝的区别:

浅拷贝:只拷贝一层,更深层的对象级别的只拷贝引用

深拷贝:拷贝多层,每一级别的数据都会拷贝。这样更改拷贝值就不影响另外的对象

ES6浅拷贝方法:Object.assign(target,…sources)

  1. let obj={
  2. id:1,
  3. name:'Tom',
  4. msg:{
  5. age:18
  6. }
  7. }
  8. let o={}
  9. //实现深拷贝 递归 可以用于生命游戏那个题对二维数组的拷贝,
  10. //但比较麻烦,因为已知元素都是值,直接复制就行,无需判断
  11. function deepCopy(newObj,oldObj){
  12. for(var k in oldObj){
  13. let item=oldObj[k]
  14. //判断是数组?对象?简单类型?
  15. if(item instanceof Array){
  16. newObj[k]=[]
  17. deepCopy(newObj[k],item)
  18. }else if(item instanceof Object){
  19. newObj[k]={}
  20. deepCopy(newObj[k],item)
  21. }else{ //简单数据类型,直接赋值
  22. newObj[k]=item
  23. }
  24. }
  25. }

8. 手写call, apply, bind

手写call

  1. Function.prototype.myCall=function(context=window){ // 函数的方法,所以写在Fuction原型对象上
  2. if(typeof this !=="function"){ // 这里if其实没必要,会自动抛出错误
  3. throw new Error("不是函数")
  4. }
  5. const obj=context||window //这里可用ES6方法,为参数添加默认值,js严格模式全局作用域this为undefined
  6. obj.fn=this //this为调用的上下文,this此处为函数,将这个函数作为obj的方法
  7. const arg=[...arguments].slice(1) //第一个为obj所以删除,伪数组转为数组
  8. res=obj.fn(...arg)
  9. delete obj.fn // 不删除会导致context属性越来越多
  10. return res
  11. }
  12. //用法:f.call(obj,arg1)
  13. function f(a,b){
  14. console.log(a+b)
  15. console.log(this.name)
  16. }
  17. let obj={
  18. name:1
  19. }
  20. f.myCall(obj,1,2) //否则this指向window
  21. obj.greet.call({name: 'Spike'}) //打出来的是 Spike

手写apply(arguments[this, [参数1,参数2…..] ])

  1. Function.prototype.myApply=function(context){ // 箭头函数从不具有参数对象!!!!!这里不能写成箭头函数
  2. let obj=context||window
  3. obj.fn=this
  4. const arg=arguments[1]||[] //若有参数,得到的是数组
  5. let res=obj.fn(...arg)
  6. delete obj.fn
  7. return res
  8. }
  9. function f(a,b){
  10. console.log(a,b)
  11. console.log(this.name)
  12. }
  13. let obj={
  14. name:'张三'
  15. }
  16. f.myApply(obj,[1,2]) //arguments[1]

手写bind

  1. this.value = 2
  2. var foo = {
  3. value: 1
  4. };
  5. var bar = function(name, age, school){
  6. console.log(name) // 'An'
  7. console.log(age) // 22
  8. console.log(school) // '家里蹲大学'
  9. }
  10. var result = bar.bind(foo, 'An') //预置了部分参数'An'
  11. result(22, '家里蹲大学') //这个参数会和预置的参数合并到一起放入bar中

简单版本

  1. Function.prototype.bind = function(context, ...outerArgs) {
  2. var fn = this;
  3. return function(...innerArgs) { //返回了一个函数,...rest为实际调用时传入的参数
  4. return fn.apply(context,[...outerArgs, ...innerArgs]); //返回改变了this的函数,
  5. //参数合并
  6. }
  7. }

new失败的原因:

例:

  1. // 声明一个上下文
  2. let thovino = {
  3. name: 'thovino'
  4. }
  5. // 声明一个构造函数
  6. let eat = function (food) {
  7. this.food = food
  8. console.log(`${this.name} eat ${this.food}`)
  9. }
  10. eat.prototype.sayFuncName = function () {
  11. console.log('func name : eat')
  12. }
  13. // bind一下
  14. let thovinoEat = eat.bind(thovino)
  15. let instance = new thovinoEat('orange') //实际上orange放到了thovino里面
  16. console.log('instance:', instance) // {}

生成的实例是个空对象

在new操作符执行时,我们的thovinoEat函数可以看作是这样:

  1. function thovinoEat (...innerArgs) {
  2. eat.call(thovino, ...outerArgs, ...innerArgs)
  3. }

在new操作符进行到第三步的操作thovinoEat.call(obj, …args)时,这里的obj是new操作符自己创建的那个简单空对象{},但它其实并没有替换掉thovinoEat函数内部的那个上下文对象thovino。这已经超出了call的能力范围,因为这个时候要替换的已经不是thovinoEat函数内部的this指向,而应该是thovino对象。

换句话说,我们希望的是new操作符将eat内的this指向操作符自己创建的那个空对象。但是实际上指向了thovino,new操作符的第三步动作并没有成功!

可new可继承版本

  1. Function.prototype.bind = function (context, ...outerArgs) {
  2. let that = this;
  3. function res (...innerArgs) {
  4. if (this instanceof res) {
  5. // new操作符执行时
  6. // 这里的this在new操作符第三步操作时,会指向new自身创建的那个简单空对象{}
  7. that.call(this, ...outerArgs, ...innerArgs)
  8. } else {
  9. // 普通bind
  10. that.call(context, ...outerArgs, ...innerArgs)
  11. }
  12. }
  13. res.prototype = this.prototype //!!!
  14. return res
  15. }

9. 手动实现new

new的过程文字描述:

  1. 创建一个空对象 obj;
  2. 将空对象的隐式原型(proto)指向构造函数的prototype。
  3. 使用 call 改变 this 的指向
  4. 如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。

    function Person(name,age){
    this.name=name
    this.age=age
    }
    Person.prototype.sayHi=function(){
    console.log(‘Hi!我是’+this.name)
    }
    let p1=new Person(‘张三’,18)

    手动实现new
    function create(){
    let obj={}
    //获取构造函数
    let fn=[].shift.call(arguments) //将arguments对象提出来转化为数组,arguments并不是数组而是对象 !!!这种方法删除了arguments数组的第一个元素,!!这里的空数组里面填不填元素都没关系,不影响arguments的结果 或者let arg = [].slice.call(arguments,1)
    obj.proto=fn.prototype
    let res=fn.apply(obj,arguments) //改变this指向,为实例添加方法和属性
    //确保返回的是一个对象(万一fn不是构造函数)
    return typeof res===’object’?res:obj
    }

    let p2=create(Person,’李四’,19)
    p2.sayHi()

细节:

  1. [].shift.call(arguments) 也可写成:
  2. let arg=[...arguments]
  3. let fn=arg.shift() //使得arguments能调用数组方法,第一个参数为构造函数
  4. obj.__proto__=fn.prototype
  5. //改变this指向,为实例添加方法和属性
  6. let res=fn.apply(obj,arg)

10. 手写promise(常见promise.all, promise.race)

  1. // Promise/A+ 规范规定的三种状态
  2. const STATUS = {
  3. PENDING: 'pending',
  4. FULFILLED: 'fulfilled',
  5. REJECTED: 'rejected'
  6. }
  7. class MyPromise {
  8. // 构造函数接收一个执行回调
  9. constructor(executor) {
  10. this._status = STATUS.PENDING // Promise初始状态
  11. this._value = undefined // then回调的值
  12. this._resolveQueue = [] // resolve时触发的成功队列
  13. this._rejectQueue = [] // reject时触发的失败队列
  14. // 使用箭头函数固定this(resolve函数在executor中触发,不然找不到this)
  15. const resolve = value => {
  16. const run = () => {
  17. // Promise/A+ 规范规定的Promise状态只能从pending触发,变成fulfilled
  18. if (this._status === STATUS.PENDING) {
  19. this._status = STATUS.FULFILLED // 更改状态
  20. this._value = value // 储存当前值,用于then回调
  21. // 执行resolve回调
  22. while (this._resolveQueue.length) {
  23. const callback = this._resolveQueue.shift()
  24. callback(value)
  25. }
  26. }
  27. }
  28. //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以实现promise异步调用的特性(规范上是微任务,这里是宏任务)
  29. setTimeout(run)
  30. }
  31. // 同 resolve
  32. const reject = value => {
  33. const run = () => {
  34. if (this._status === STATUS.PENDING) {
  35. this._status = STATUS.REJECTED
  36. this._value = value
  37. while (this._rejectQueue.length) {
  38. const callback = this._rejectQueue.shift()
  39. callback(value)
  40. }
  41. }
  42. }
  43. setTimeout(run)
  44. }
  45. // new Promise()时立即执行executor,并传入resolve和reject
  46. executor(resolve, reject)
  47. }
  48. // then方法,接收一个成功的回调和一个失败的回调
  49. function then(onFulfilled, onRejected) {
  50. // 根据规范,如果then的参数不是function,则忽略它, 让值继续往下传递,链式调用继续往下执行
  51. typeof onFulfilled !== 'function' ? onFulfilled = value => value : null
  52. typeof onRejected !== 'function' ? onRejected = error => error : null
  53. // then 返回一个新的promise
  54. return new MyPromise((resolve, reject) => {
  55. const resolveFn = value => {
  56. try {
  57. const x = onFulfilled(value)
  58. // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
  59. x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
  60. } catch (error) {
  61. reject(error)
  62. }
  63. }
  64. }
  65. }
  66. const rejectFn = error => {
  67. try {
  68. const x = onRejected(error)
  69. x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
  70. } catch (error) {
  71. reject(error)
  72. }
  73. }
  74. switch (this._status) {
  75. case STATUS.PENDING:
  76. this._resolveQueue.push(resolveFn)
  77. this._rejectQueue.push(rejectFn)
  78. break;
  79. case STATUS.FULFILLED:
  80. resolveFn(this._value)
  81. break;
  82. case STATUS.REJECTED:
  83. rejectFn(this._value)
  84. break;
  85. }
  86. })
  87. }
  88. catch (rejectFn) {
  89. return this.then(undefined, rejectFn)
  90. }
  91. // promise.finally方法
  92. finally(callback) {
  93. return this.then(value => MyPromise.resolve(callback()).then(() => value), error => {
  94. MyPromise.resolve(callback()).then(() => error)
  95. })
  96. }
  97. // 静态resolve方法
  98. static resolve(value) {
  99. return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value))
  100. }
  101. // 静态reject方法
  102. static reject(error) {
  103. return new MyPromise((resolve, reject) => reject(error))
  104. }
  105. // 静态all方法
  106. static all(promiseArr) {
  107. let count = 0
  108. let result = []
  109. return new MyPromise((resolve, reject) => {
  110. if (!promiseArr.length) {
  111. return resolve(result)
  112. }
  113. promiseArr.forEach((p, i) => {
  114. MyPromise.resolve(p).then(value => {
  115. count++
  116. result[i] = value
  117. if (count === promiseArr.length) {
  118. resolve(result)
  119. }
  120. }, error => {
  121. reject(error)
  122. })
  123. })
  124. })
  125. }
  126. // 静态race方法
  127. static race(promiseArr) {
  128. return new MyPromise((resolve, reject) => {
  129. promiseArr.forEach(p => {
  130. MyPromise.resolve(p).then(value => {
  131. resolve(value)
  132. }, error => {
  133. reject(error)
  134. })
  135. })
  136. })
  137. }
  138. }

11. 手写原生AJAX

步骤

  1. 创建 XMLHttpRequest 实例
  2. 发出 HTTP 请求
  3. 服务器返回 XML 格式的字符串
  4. JS 解析 XML,并更新局部页面

    不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。

了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。

version 1.0:

  1. myButton.addEventListener('click', function () {
  2. ajax()
  3. })
  4. function ajax() {
  5. let xhr = new XMLHttpRequest() //实例化,以调用方法
  6. xhr.open('get', 'https://www.google.com') //参数2,url。参数三:异步
  7. xhr.onreadystatechange = () => { //每当 readyState 属性改变时,就会调用该函数。
  8. if (xhr.readyState === 4) { //XMLHttpRequest 代理当前所处状态。
  9. if (xhr.status >= 200 && xhr.status < 300) { //200-300请求成功
  10. let string = request.responseText
  11. //JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象
  12. let object = JSON.parse(string)
  13. }
  14. }
  15. }
  16. request.send() //用于实际发出 HTTP 请求。不带参数为GET请求
  17. }

promise实现

  1. function ajax(url) {
  2. const p = new Promise((resolve, reject) => {
  3. let xhr = new XMLHttpRequest()
  4. xhr.open('get', url)
  5. xhr.onreadystatechange = () => {
  6. if (xhr.readyState == 4) {
  7. if (xhr.status >= 200 && xhr.status <= 300) {
  8. resolve(JSON.parse(xhr.responseText))
  9. } else {
  10. reject('请求出错')
  11. }
  12. }
  13. }
  14. xhr.send() //发送hppt请求
  15. })
  16. return p
  17. }
  18. let url = '/data.json'
  19. ajax(url).then(res => console.log(res))
  20. .catch(reason => console.log(reason))

12. 手写节流防抖函数

防抖:

  1. function debounce(fn, delay) {
  2. if(typeof fn!=='function') {
  3. throw new TypeError('fn不是函数')
  4. }
  5. let timer; // 维护一个 timer
  6. return function () {
  7. var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象)
  8. var args = arguments;
  9. if (timer) {
  10. clearTimeout(timer);
  11. }
  12. timer = setTimeout(function () {
  13. fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);
  14. }, delay);
  15. };
  16. }
  17. input1.addEventListener('keyup', debounce(() => {
  18. console.log(input1.value)
  19. }), 600)

节流:

  1. function throttle(fn, delay) {
  2. let timer;
  3. return function () {
  4. var _this = this;
  5. var args = arguments;
  6. if (timer) {
  7. return;
  8. }
  9. timer = setTimeout(function () {
  10. fn.apply(_this, args); // 这里args接收的是外边返回的函数的参数,不能用arguments
  11. // fn.apply(_this, arguments); 需要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。
  12. timer = null; // 在delay后执行完fn之后清空timer,此时timer为假,throttle触发可以进入计时器
  13. }, delay)
  14. }
  15. }
  16. div1.addEventListener('drag', throttle((e) => {
  17. console.log(e.offsetX, e.offsetY)
  18. }, 100))

13. 手写Promise加载图片

  1. function getData(url) {
  2. return new Promise((resolve, reject) => {
  3. $.ajax({
  4. url,
  5. success(data) {
  6. resolve(data)
  7. },
  8. error(err) {
  9. reject(err)
  10. }
  11. })
  12. })
  13. }
  14. const url1 = './data1.json'
  15. const url2 = './data2.json'
  16. const url3 = './data3.json'
  17. getData(url1).then(data1 => {
  18. console.log(data1)
  19. return getData(url2)
  20. }).then(data2 => {
  21. console.log(data2)
  22. return getData(url3)
  23. }).then(data3 =>
  24. console.log(data3)
  25. ).catch(err =>
  26. console.error(err)
  27. )

14. 函数实现一秒钟输出一个数

  1. for(let i=0;i<=10;i++){ //用var打印的都是11
  2. setTimeout(()=>{
  3. console.log(i);
  4. },1000*i)
  5. }

15. 创建10个标签,点击的时候弹出来对应的序号?

  1. var a
  2. for(let i=0;i<10;i++){
  3. a=document.createElement('a')
  4. a.innerHTML=i+'<br>'
  5. a.addEventListener('click',function(e){
  6. console.log(this) //this为当前点击的<a>
  7. e.preventDefault() //如果调用这个方法,默认事件行为将不再触发。
  8. //例如,在执行这个方法后,如果点击一个链接(a标签),浏览器不会跳转到新的 URL 去了。我们可以用 event.isDefaultPrevented() 来确定这个方法是否(在那个事件对象上)被调用过了。
  9. alert(i)
  10. })
  11. const d=document.querySelector('div')
  12. d.appendChild(a) //append向一个已存在的元素追加该元素。
  13. }

声明:文章著作权归作者所有,如有侵权,请联系小编删除。

708d96e6c3d389de71898a3165bd6930.png

往期推荐

大厂面试过程复盘(微信/阿里/头条,附答案篇)

791fba728b756e210119e1f5007e572d.png

面试题:说说事件循环机制(满分答案来了)

66b2ccc535836cec2733f5bc70c97a6b.png

专心工作只想搞钱的前端女程序员的2020

4807c2a49c40dfa8c66e3eb5d6694a41.png

最后

  • 欢迎加我微信,拉你进技术群,长期交流学习…

  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人…

e38d06c1cd0181cf4d2c116ff029f9af.png

012c3f47a37d2277fe6ac59623a792d7.png

点个在看支持我吧

fbb04959d5939cc4d54dfb70e963895c.gif

发表评论

表情:
评论列表 (有 0 条评论,316人围观)

还没有评论,来说两句吧...

相关阅读