「offer来了」JavaScript篇,保姆级巩固你的js知识体系(二)

简介: 下面开始本文的讲解~

🤪五、this问题


1、描述下this(谈谈对this对象的理解)

  • this ,函数执行的上下文,总是指向函数的直接调用者(而非间接调用者),可以通过 applycallbind 改变 this 的指向。
  • 如果有 new 关键字,this 指向 new 出来的那个对象。
  • 在事件中,this 指向触发这个事件的对象,特殊的是,IE 中的 attachEvent 中的 this 总是指向全局对象 window
  • 对于匿名函数或者直接调用的函数来说,this 指向全局上下文(浏览器为 windowNodeJSglobal),剩下的函数调用,那就是谁调用它, this 就指向谁。
  • 对于 es6 的箭头函数,箭头函数的指向取决于该箭头函数声明的位置,在哪里声明, this 就指向哪里。


2、this绑定的四大规则

this绑定四大规则遵循以下顺序:

New 绑定 > 显示绑定 > 隐式绑定 > 默认绑定

下面一一介绍四大规则。


(1)New绑定

  • New 绑定new 调用函数会创建一个全新的对象,并将这个对象绑定到函数调用的 thisNew 绑定时,如果是 new 一个硬绑定函数,那么会用 new 新建的对象替换这个硬绑定 this具体实现代码如下:
function foo(a) { 
    this.a = a; 
}
var bar = new foo(2); 
console.log(bar.a); //2
复制代码


(2)显式绑定

  • 显示绑定:通过在函数上运行 callapply ,来显示绑定的 this具体实现代码如下:
function foo() { 
    console.log(this.a); 
}
var obj = { 
    a: 2 
};
foo.call(obj); //2
复制代码
  • 显示绑定之硬绑定
function foo(something) { 
    console.log(this.a, something); 
    return this.a + something; 
}
function bind(fn, obj) { 
    return function() { 
        return fn.apply(obj, arguments); 
    }; 
}
var obj = { 
    a: 2 
}
var bar = bind(foo, obj);
复制代码


(3)隐式绑定

  • 隐式绑定:调用位置是否有上下文对象,或者是否被某个对象拥有或者包含,那么隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。而且,对象属性链只有上一层或者说最后一层在调用位置中起作用。具体实现代码如下:
function foo() { 
    console.log(this.a); 
}
var obj = { 
    a: 2, 
    foo: foo, 
}
obj.foo(); // 2
复制代码


(4)默认绑定

  • 默认绑定:没有其他修饰( bindapplycall ),在非严格模式下定义指向全局对象,在严格模式下定义指向 undefined具体实现代码如下:
function foo() { 
    console.log(this.a); 
}
var a = 2; 
foo(); //undefined
复制代码


3、如果一个构造函数,bind了一个对象,用这个构造函数创建出的实例会继承这个对象的属性吗?为什么?

不会继承,因为根据 this 绑定四大规则,new 绑定的优先级高于 bind 显示绑定,通过 new 进行构造函数调用时,会创建一个新对象,这个新对象会代替 bind 的对象绑定,作为此函数的 this,并且在此函数没有返回对象的情况下,返回这个新建的对象。


4、箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?


(1)箭头函数和普通函数定义

普通函数通过 function 关键字定义, this 无法结合词法作用域使用,在运行时绑定,只取决于函数的调用方式,在哪里被调用,调用位置。(取决于调用者,和是否独立运行)。

箭头函数使用被称为胖箭头的操作 => 来定义,箭头函数不应用普通函数 this 绑定的四种规则,而是根据外层(函数或全局)的作用域来决定 this ,且箭头函数的绑定无法被修改new 也不行)。


(2)箭头函数和普通函数的区别

  • 箭头函数常用于回调函数中,包括事件处理器定时器
  • 箭头函数和 var self = this ,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
  • 没有原型、没有 this 、没有 super,没有 arguments ,没有 new.target
  • 不能通过 new 关键字调用。
  • 一个函数内部有两个方法:[[Call]][[Construct]],在通过 new 进行函数调用时,会执行 [[construct]]  方法,创建一个实例对象,然后再执行这个函数体,将函数的 this 绑定在这个实例对象上。
  • 当直接调用时,执行 [[Call]] 方法,直接执行函数体
  • 箭头函数没有 [[Construct]] 方法,不能被用作构造函数调用,当使用 new 进行函数调用时会报错。
function foo(){
    return (a) => {
        console.log(this.a);
    }
}
let obj1 = {
    a: 2
};
let obj2 = {
    a: 3
};
let bar1 = foo.call(obj1);
let bar2 = bar1.call(obj2);
console.log(bar1); // object
console.log(bar2); // 2 undefind
复制代码


5、了解this嘛,apply,call,bind具体指什么?


(1)三者的区别

  • applycallbind 三者都是函数的方法,都可以改变函数的 this 指向。
  • applycall 都是改变函数 this 指向,并在传入参数后立即调用执行该函数。
  • bind 是在改变函数 this 指向后,并传入参数后返回一个新的函数,不会立即调用执行。


(2)传参方式

apply 传入的参数是数组形式的,call 传入的参数是按顺序的逐个传入并以逗号隔开bind 传入的参数既可以是数组形式,也可以是按顺序逐个传入具体方式见下方:

apply: 
Array.prototype.apply(this, [args1, args2]) 
ES6 之前用来展开数组调用,  foo.apply(null, []) , ES6 之后使用 ... 操作符;
call: 
Array.prototype.call(this, args1, args2)。
bind: 
Array.prototype.bind(this, args1, args2);
Array.prototype.bind(this, [args1, args2])。
复制代码


(3)手写apply、call、bind

apply:

// 实现apply函数,在函数原型上封装myApply函数 , 实现和原生apply函数一样的效果
Function.prototype.myApply = function(context){
    // 存储要转移的目标对象
    _this = context ? Object(context) : window;
    // 在转移this的对象上设定一个独一无二的属性,并将函数赋值给它
    let key = Symbol('key');
    _this[key] = this;
    // 将数组里存储的参数拆分开,作为参数调用函数
    let res = arguments[1] ? _this[key](...arguments[1]) : _this[key]();
    // 删除
    delete _this[key];
    // 返回函数返回值
    return res;
}
// 测试代码
let obj = {
    'name': '张三'
}
function showName(first, second, third){
    console.log(first, second, third);
    console.log(this.name);
}
showName.myApply(obj, [7,8,9]);
复制代码

call:

// 实现call函数,在函数原型上封装myCall函数 , 实现和原生call函数一样的效果
Function.prototype.myCall = function(context){
    // 存储要转移的目标对象
    let _this = context ? Object(context) : window;
    // 在转移this的对象上设定一个独一无二的属性,并将函数赋值给它
    let  key = Symbol('key');
    _this[key] = this;
    // 创建空数组,存储多个传入参数
    let args = [];
    // 将所有传入的参数添加到新数组中
    for(let i =1; i < arguments.length; i++){
        args.push(arguments[i]);
    }
    // 将新数组拆开作为多个参数传入,并调用函数
    let res = _this[key](...args);
    // 删除
    delete _this[key];
    // 返回函数返回值
    return res;
}
let obj = {
    'name': '张三'
}
function showName(first, second, third){
    console.log(first, second, third);
    console.log(this.name);
}
showName.myCall(obj, 7, 8, 9);
复制代码

bind:

// 实现Bind函数,在函数原型上封装myBind函数 , 实现和原生bind函数一样的效果
Function.prototype.myBind = function(context){
    // 存储要转移的目标对象
    let _this = context ? Object(context) : window;
    // 在转移this的对象上设定一个独一无二的属性,并将函数赋值给它
    let key = Symbol('key');
    _this[key] = this;
    // 创建函数闭包
    return function(){
        // 将所有参数先拆分开,再添加到新数组中,以此来支持多参数传入以及数组参数传入的需求
        let args = [].concat(...arguments);
        // 调用函数
        let res = _this[key](...args);
        // 删除
        delete _this[key];
        // 返回函数返回值
        return res;
    }
}
// 测试代码
let obj = {
    'name' : '张三'
}
function showName(first, second, third){
    console.log(first, second, third);
    console.log(this.name);
}
showName.myBind(obj)([7,8,9]);
复制代码


😋六、Ajax问题


1、Ajax原理

  • Ajax 的原理简单来说是在用户服务器之间加了一个中间层(AJAX引擎),通过 XMLHTTPRequest 对象来向服务器发起异步请求,从服务器获得数据,然后用 javascript 来操作 DOM 而更新页面。
  • 使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
  • Ajax 的过程只涉及 JavascriptXMLHttpRequestDOM ,其中 XMLHttpRequestajax 的核心机制。


2、Ajax解决浏览器缓存问题

  • ajax 发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")
  • ajax 发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")
  • URL 后面加上一个随机数: "fresh=" + Math.random()
  • URL 后面加上时间搓:"nowtime=" + new Date().getTime()


3、js单线程

  • 单线程:只有一个线程,只能做一件事情。
  • 原因:避免 DOM 渲染的冲突。
  • 浏览器需要渲染 DOM
  • JS 可以修改 DOM 结构;
  • JS 执行的时候,浏览器 DOM 渲染会暂停;
  • 两段 JS 也不能同时执行(都修改 DOM 就冲突了);
  • Webworker 支持多线程,但是不能访问 DOM
  • 解决方案:异步。


4、异步编程的实现方式


(1)回调函数

  • 优点:简单、容易理解
  • 缺点:不利于维护,代码耦合高


(2)事件监听(采用时间驱动模式,取决于某个事件是否发生)

  • 优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
  • 缺点:事件驱动型,流程不够清晰


(3)发布/订阅(观察者模式)

  • 类似于事件监听,但是可以通过“消息中心”,了解现在有多少发布者,多少订阅者


(4)Promise 对象

  • 优点:可以利用 then 方法,进行链式写法;可以书写错误时的回调函数
  • 缺点:编写和理解,相对比较难


(5)Generator 函数

  • 优点:函数体内外的数据交换错误处理机制
  • 缺点:流程管理不方便


(6)async 函数

  • 优点:内置执行器、更好的语义、更广的适用性、返回的是 Promise 、结构清晰。
  • 缺点:错误处理机制


6、关于window.onload 和 DOMContentLoaded

window.addEventListener('load', function(){
    //页面的全部资源加载完才会执行,包括图片、视频等
});
document.addEventListener('DOMContentLoaded', function(){
    //DOM 渲染完即可执行,此时图片、视频还可能没加载完 -> 尽量选择此方法
});
复制代码


7、ajax、axios、fetch区别


(1)ajax

  • 本身是针对 MVC 的编程,不符合现在前端 MVVM 的浪潮。
  • 基于原生的 XHR 开发, XHR 本身的架构不清晰,已经有了 fetch 的替代方案。
  • JQuery 整个项目太大,单纯使用 ajax 却要引入整个 JQuery 非常的不合理(采取个性化打包的方案又不能享受 CDN 服务)。


(2)axios

  • 从浏览器中创建 XMLHttpRequest
  • node.js 发出 http 请求。
  • 支持 Promise API
  • 拦截请求和响应。
  • 转换请求和响应数据。
  • 取消请求。
  • 自动转换 JSON 数据。
  • 客户端支持防止 CSRF/XSRF


(3)fetch

  • fetch 返回的 promise 不会被标记为 reject ,即使该 http 响应的状态码是 404500 。仅当网络故障或请求被阻止时,才会标记为 reject
  • 只对网络请求报错,对 400500 都当做成功的请求,需要封装去处理。
  • 这里对于 cookie 的处理比较特殊,不同浏览器对 credentials 的默认值不一样,也就使得默认情况下 cookie 变的不可控。
  • 本身无自带 abort ,无法超时控制,可以使用 AbortController 解决取消请求问题。
  • 没有办法原生监测请求的进度,而 XHR 可以。


8、手写题:手写Ajax函数

/*
  1. get()方法
     参数:url(请求的地址)、data(携带数据)、callback(成功回调函数)、dataType(返回数据类型)
  2. post()方法
     参数:url(请求的地址)、data(携带数据)、callback(成功回调函数)、dataType(返回数据类型)
  3. ajax()方法
     参数:obj(对象中包含了各种参数),其中有url、data、dataType、async、type
*/
let $ = {
  createXHR: function() {
    if(window.XMLHttpRequest) {
      return new XMLHttpRequest()
    } else {
      return new ActiveXObject()
    } 
  },
  get: function(url, data, callback, dataType) {
    let dataType = dataType.toLowerCase()
    if(data) {
      url += '?'
      Object.keys(data).forEach(key => url += `${key}=${data[key]}&`)
      url = url.slice(0, -1)
    }
    let xhr = this.createXHR()
    xhr.open('get', url)
    xhr.send()
    xhr.onreadystatechange = function() {
      if(xhr.readyState === 4) {
        if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
          let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText
          callback(res, xhr.status, xhr)
        }
      }
    }
  },
  post: function(url, data, callback, dataType) {
    let dataType = dataType.toLowerCase()
    let xhr = this.createXHR()
    let str = ''
    if(data) {
      Object.keys(data).forEach(key => str += `${key}=${data[key]}&`)
      str = str.slice(0, -1)
    }
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    xhr.send(str)
    xhr.onreadystatechange = function() {
      if(xhr.readyState === 4) {
        if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
          let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText
          callback(res, xhr.status, xhr)
        }
      }
    }
  },
  ajax: function(params) {
    // 初始化参数
    let type = params.type ? params.type.toLowerCase() : 'get'
    let isAsync = params.isAsync ? params.isAsync : 'true'
    let url = params.url
    let data = params.data ? params.data : {}
    let dataType = params.dataType.toLowerCase()
    let xhr = this.createXHR()
    let str = ''
    // 拼接字符串
    Object.keys(data).forEach(key => str += `${key}=${data[key]}&`)
    str = str.slice(0, -1)
    if(type === 'get') url += `?${str}`;
    return new Promise((resolve, reject) => {
      // 创建请求
      xhr.open(type, url, isAsync)
      if(type === 'post') {
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-rulencoded')
        xhr.send(str)
      } else {
        xhr.send()
      }
      xhr.onreadystatechange = function() {
        if(xhr.readyState === 4) {
          if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
            let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText
            resolve(res) // 请求成功,返回数据
          } else {
            reject(xhr.status) // 请求失败,返回状态码
          }
        }
      }
    })  
  }
}
复制代码


9、手写题:手写Promise原理

class MyPromise{
    constructor(fn){
        this.resolvedCallbacks = [];
        this.rejectCallbacks = [];
        // pending 即在等待状态
        this.state = 'PENDING';
        this.value = '';
        fn(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(value){
        if(this.state === 'PENDING'){
            this.state = 'RESOLVED';
            this.value = value;
            this.resolvedCallbacks.map(cb => cb(value));
        }
    }
    reject(value){
        if(this.state === 'PENDING'){
            this.state = 'REJECTED';
            this.value = value;
            this.rejectCallbacks.map(cb => cb(value));
        }
    }
    then(onFulfilled, onRejected){
        if(this.state === 'PENDING'){
            this.resolvedCallbacks.map(cb => cb(onFulfilled));
            this.rejectCallbacks.map(cb => cb(onRejected));
        }
        if(this.state === 'RESOLVED'){
            onFulfilled(this.value);
        }
        if(this.state === 'REJECTED'){
            onRejected(this.value);
        }
    }
}
复制代码


10、手写题:基于Promise手写Promise.all

/**
 * promise的三种状态:
 * 1.pending:等待状态,比如正在网络请求,或定时器没有到时间;
 * 2.fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调then函数
 * 3.reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调catch函数
 */
/*----------------------------------------------------------------- */
// 函数.then()
// 函数then是Promise中的一个方法,它会在Promise处于fulfill状态时调用触发
// resolve和reject是默认传入的函数参数
new Promise((resolve, reject) => {
    setTimeout(() => {
        // 在Promise中调用resolve函数,会使Promise变为fulfill状态
        // resolve函数可以传入一个参数,作为then函数的默认传入参数
        resolve('成功');
    }, 1000);
})
.then(data => {
    console.log(data); //结果输出成功
});
/*----------------------------------------------------------------- */
// 函数 .catch()
// 函数catch是Promise的一个方法。它会在Promise处于reject状态时调用触发
new Promise((resolve, reject) => {
    setTimeout(() => {
        // 在Promise调用reject函数,会使Promise变为reject状态
        // reject函数可以传入一个参数,作为catch函数的默认传入参数
        reject('失败');
    }, 1000)
})
.catch(err => {
    console.log(err); //结果输出:失败
})
/*----------------------------------------------------------------- */
// 函数.finally()
// 函数finally是Promise中的一个方法,它会在Promise的最后触发,无论Promise处于什么状态
new Promise((resolve, reject) => {
    setTimeout(() => {
       resolve('成功啦!')
    },1000)
})
.then(data => {
    console.log(data);
})
.finally(() => {
    console.log('Promise结束');
})
/* 
    结果输出:成功啦!
            Promise结束
*/
/*----------------------------------------------------------------- */
// 函数all()
// 函数all是Promise中的一个方法,它用于将多个promise实例,包装成一个新的promise实例
Promise.all([
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('我是第一个异步请求的数据');
        });
    }, 1000),
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('我是第二个异步请求的数据');
        }, 1000);
    })
])
.then(results => {
    console.log(results); // ['我是第一个异步请求的数据', '我是第二个异步请求的数据']
})
/*----------------------------------------------------------------- */
// 实际应用
let string1 = 'I am';
new Promise((resolve, reject) => {
    setTimeout(() => {
        let string2 = string1 + 'Monday';
        resolve(string2);
    }, 1000);
})
.then(data => {
    return new Promise((resolve, reject) => {
        let string3 = data + 'in CSDN';
        resolve(string3);
    })
})
.then(data => {
    console.log(data);
})
.finally(() => {
    console.log('Promise结束');
})
/*
    输出结果:
    I am Monday in CSDN!
    Promise结束
*/
复制代码

详解文章补充(Promise)👇


🥰七、手写题补充


1、性能优化相关


(1)手写节流函数

节流:每隔一段时间执行一次,通常用在高频率触发的地方,降低频率。—— 如:鼠标滑动、拖拽

通俗来讲,节流是从频繁触发执行变为每隔一段时间才执行一次。

//封装节流函数,实现节流
function throttle(func, delay=500) {
    let timer = null;
    let status = false;
    return function (...args) {
        if(status) return;
        status = true;
        timer = setTimeout(() => {
            func.apply(this, args)
            status = false
        }, delay);
    }
}
复制代码


(2)手写防抖函数

防抖:一段时间内连续触发,不执行,直到超出限定时间执行最后一次。—— 如:  inputscroll 滚动

通俗来讲,防抖是从频繁触发执行变为最后一次才执行。

//封装防抖函数,实现防抖
function denounce(func, delay=500){
    let timer = null;
    return function(...args){
        // 如果有值,清除定时器,之后继续执行
        if(timer){
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            func.apply(this, args);
        },delay);
    }
}
复制代码


(3)图片懒加载

定义:

懒加载突出一个“懒”字,懒就是拖延迟的意思,所以“懒加载”说白了就是延迟加载,比如我们加载一个页面,这个页面很长很长,长到我们的浏览器可视区域装不下,那么懒加载就是优先加载可视区域的内容,其他部分等进入了可视区域在加载。

代码实现:

let img = document.getElementsByTagName('img'); // 获取img标签相关的
let num = img.length; // 记录有多少张图片
let count = 0; // 计数器,从第一张图片开始计数
lazyload(); // 首次加载别忘了加载图片
function lazyload() {
    let viewHeight = document.documentElement.clientHeight; // clientHeight 获取屏幕可视区域的高度
    let scrollHeight = document.documentElement.scrollTop || document.body.scrollTop; // 滚动条卷去的高度
    for (let i = 0; i < num; i++) {
        // 元素现在已经出现在视觉区域中
        if (img[i].offsetTop < scrollHeight + viewHeight) {
            // 当src不存在时,跳出本轮循环,继续下一轮
            if (img[i].getAttribute('src') !== 'default.jpg') {
                continue;
            } else {
                // 当src属性存在时,获取src的值,并将其赋值给img
                img[i].src = img[i].getAttribute('data-src');
                count++;
            }
        }
    }
}
复制代码


2、原生API手写


(1)forEach

用法:

forEach() 方法对数组的每个元素执行一次给定的函数。原生 API 具体解析如下:

arr.forEach(function(currentValue, currentIndex, arr) {}, thisArg)
//currentValue  必需。当前元素
//currentIndex  可选。当前元素的索引
//arr           可选。当前元素所属的数组对象。
//thisArg       可选参数。当执行回调函数时,用作 this 的值。
复制代码

代码实现:

Array.prototype.myForEach = function (fn, thisArg) {
    if (typeof fn !== 'function') {
        throw new Error('参数必须为函数');
    }
    if (!Array.isArray(this)) {
        throw new Error('只能对数组使用forEach方法');
    }
    let arr = this;
    for (let i = 0; i < arr.length; i++) {
        fn.call(thisArg, arr[i], i, arr);
    }
}
// 测试
let arr = [1, 2, 3, 4, 5];
arr.myForEach((item, index) => {
    console.log(item, index);
});
// 测试 thisArg
function Counter() {
    this.sum = 0;
    this.count = 0;
}
// 因为 thisArg 参数(this)传给了forEach(),每次调用时,它都被传给 callback 函数,作为它的 this 值
Counter.prototype.add = function (array) {
    array.myForEach(function (entry) {
        this.sum += entry;
        ++this.count;
    }, this);
}
const obj = new Counter();
obj.add([2, 5, 9]);
console.log(obj.count); // 3 === (1 + 1 + 1)
console.log(obj.sum); // 16 === (2 + 5 + 9)
复制代码


(2)map

用法:

map 函数会依次处理数组中的每一个元素,并返回一个新的数组,且对原来的数组不会产生影响。

array.map(function(currentValue,index,arr){})
复制代码

代码实现:

Array.prototype.myMap = function (arr, mapCallback) {
    // 检查参数是否正确
    if (!Array.isArray(arr) || !Array.length || typeof mapCallback !== 'function') {
        return [];
    } else {
        let result = [];
        for (let i = 0; len = arr.length; i++) {
            result.push(mapCallback(arr[i], i, arr));
        }
        return result;
    }
}
// 测试
let arr = [1, 2, 3, 4, 5];
arr.map((item, index) => {
    console.log(item * 2);
}); // 2, 4, 6, 8, 10
复制代码


(3)filter

用法:

filter() 方法返回执行结果为true的项组成的数组

arr.filter(function(item, index, arr){}, context)
复制代码

代码实现:

Array.prototype.myFilter = function (fn, context) {
    if (typeof fn !== 'function') {
        throw new Error(`${fn} is not a function`);
    }
    let arr = this;
    let temp = [];
    for (let i = 0; i < arr.length; i++) {
        let result = fn.call(context, arr[i], i, arr);
        // 判断条件是否为真
        if (result) {
            temp.push(arr[i]);
        }
    }
    return temp;
}
// 测试
let arr = [1, 2, 3, 4, 5, 'A', 'B', 'C'];
console.log(arr.myFilter((item) => typeof item === 'string')); // [ 'A', 'B', 'C' ]
复制代码


(4)reduce

用法:

  • 参数: 一个回调函数,一个初始化参数 (非必须)
  • 回调函数参数有 4 个值( res : 代表累加值, cur : 目前值, index : 第几个, arr :调用 reduce 的数组)
  • 整体返回 res 累加值
arr.reduce((res,cur, index, arr) => res+cur, 0)
复制代码

代码实现:

/**
 * 
 * @param {fn} callback res→代表累加值,cur→目前值,index→第几个,arr→调用reduce的数组
 * @param {*} initialValue 初始化参数(可选)
 */
Array.prototype.myReduce = function (cb, initValue) {
    if (!Array.isArray(this)) {
        throw new TypeError("not a array");
    }
    // 数组为空,并且有初始值,报错
    if (this.length === 0 && arguments.length < 2) {
        throw new TypeError('Reduce of empty array with no initial value');
    }
    let arr = this;
    let res = null;
    // 判断有没有初始值
    if (arguments.length > 1) {
        res = initValue;
    } else {
        res = arr.splice(0, 1)[0]; //没有就取第一个值
    }
    arr.forEach((item, index) => {
        res = cb(res, item, index, arr); // cb 每次执行完都会返回一个新的 res值,覆盖之前的 res
    })
    return res;
};
// 测试结果
let arr = [1, 2, 3, 4];
let result = arr.myReduce((res, cur) => {
    return res + cur;
})
console.log(result); // 10
复制代码


3、其余手写题


(1)JSONP的实现

JSONP 的原理:JSONP 的出现使得 script 标签不受同源策略约束,用来进行跨域请求,优点是兼容性好,缺点就是只能用于 GET 请求

const jsonp = ({ url, params, callbackName }) => {
    const generateUrl = () => {
        let dataSrc = '';
        for (let key in params) {
            if (params.hasOwnProperty(key)) {
                dataSrc += `${key}=${params[key]}&`;
            }
        }
        dataSrc += `callback=${callbackName}`;
        return `${url}?${dataSrc}`;
    }
    return new Promise((resolve, reject) => {
        const scriptElement = document.createElement('script')
        scriptElement.src = generateUrl()
        document.body.appendChild(scriptElement)
        window[callbackName] = data => {
            resolve(data)
            document.removeChild(scriptElement)
        }
    })
}
复制代码


(2)Object.create

用法:

Object.creat(object[,propertiesObject]) ,用于创建一个新对象,且这个新对象继承 object 的属性。第二个参数 propertyObject 也是对象,是一个可选参数,它旨在为新创建的对象指定属性对象。该属性对象可能包含以下值:

属性 说明
configurable 表示新创建的对象是否是可配置的,即对象的属性是否可以被删除或修改,默认false
enumerable 对象属性是否可枚举的,即是否可以枚举,默认false
writable 对象是否可写,是否或以为对象添加新属性,默认false
get 对象getter函数,默认undefined
set 对象setter函数,默认undefined

代码实现:

/**
 * 
 * @param {*} proto 新创建对象的原型对象
 * @param {*} propertyObject 要定义其可枚举属性或修改的属性描述符的对象
 * @returns 
 */
Object.create2 = function (proto, propertyObject = undefined) {
    if (typeof proto !== 'object' && typeof proto !== 'function') {
        throw new TypeError('Object prototype may only be an Object or null.')
    }
    // 创建一个空的构造函数 F
    function F() { }
    // F 原型指向 proto
    F.prototype = proto
    // 创建 F 的实例
    const obj = new F()
    // propertiesObject有值则调用 Object.defineProperties
    if (propertyObject != undefined) {
        Object.defineProperties(obj, propertyObject)
    }
    if (proto === null) {
        // 创建一个没有原型对象的对象,Object.create(null)
        obj.__proto__ = null
    }
    // 返回 这个 obj
    return obj
}
const person = {
    name: 'monday',
    printIntroduction: function() {
        console.log(`My name is ${this.name}, and my age is ${this.age}`);
    }
};
const me = Object.create2(person);
me.name = 'Tuesday'; 
me.age = 18; 
me.printIntroduction();
复制代码


(3)Object.assign

用法:

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。

代码实现:

Object.assign2 = function (target, ...source) {
    if (target == null) {
        throw new TypeError('Cannot convert undefined or null to object');
    }
    let res = Object(target);
    source.forEach(function (obj) {
        if (obj != null) {
            for (let key in obj) {
                if (obj.hasOwnProperty(key)) {
                    res[key] = obj[key];
                }
            }
        }
    })
    return res;
}
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign2(target, source);
console.log(target); // { a: 1, b: 4, c: 5 }
console.log(returnedTarget); // { a: 1, b: 4, c: 5 }
复制代码


(4)手写发布订阅

代码实现:

class Subject {
    constructor(name) {
        this.name = name; // 被观察者的名字
        this.message = '今天是晴天'; // 存放一个值
        this.observers = []; // 存放所有观察者
    }
    on(observer) {
        this.observers.push(observer);
    }
    triggle(data) {
        this.message = data;
        this.observers.forEach(item => item.update(data));
    }
}
class Observer {
    constructor(name) {
        this.name = name;
    }
    update(newDate) {
        console.log(`我是观察者${this.name}: ${newDate}`);
    }
}
// 测试代码
let subject = new Subject('message');
let o1 = new Observer('小红');
let o2 = new Observer('小明');
subject.on(o1); // 我是观察者小红: 明天会下雨
subject.on(o2); // 我是观察者小明: 明天会下雨
subject.triggle('明天会下雨');
复制代码


😉八、结束语


以上收录周一整个秋招备试过程中 JavaScript 的所有面试题,上面的面试题可能还不够全,如有想要补充的内容也欢迎联系 vx:MondayLaboratory ,希望能够让文章内容更加尽善尽美,造福更多备试的小伙伴~

最后,预祝各位看到这篇文章的小伙伴们,都能够斩获到自己心仪的 offer ~


相关文章
|
1月前
|
JavaScript 前端开发
JavaScript基础&实战(1)js的基本语法、标识符、数据类型
这篇文章是JavaScript基础与实战教程的第一部分,涵盖了JavaScript的基本语法、标识符、数据类型以及如何进行强制类型转换,通过代码示例介绍了JS的输出语句、编写位置和数据类型转换方法。
JavaScript基础&实战(1)js的基本语法、标识符、数据类型
|
1月前
|
JavaScript 前端开发
JavaScript基础&实战 JS中正则表达式的使用
这篇文章介绍了JavaScript中正则表达式的使用,包括正则表达式的创建、匹配模式、字符串匹配、拆分、搜索、匹配和替换等方法,并通过示例代码展示了如何应用这些技术。
JavaScript基础&实战 JS中正则表达式的使用
|
1月前
|
JavaScript 前端开发
JavaScript基础&实战(5)js中的数组、forEach遍历、Date对象、Math、String对象
这篇文章介绍了JavaScript中的数组、Date对象、Math对象以及包装类(String、Number、Boolean),并详细讲解了数组的创建、方法(如forEach、push、pop、unshift、slice、splice)和遍历操作,以及工厂方法创建对象和原型对象的概念。
JavaScript基础&实战(5)js中的数组、forEach遍历、Date对象、Math、String对象
|
1月前
|
JavaScript 前端开发
JavaScript基础&实战(4)js中的对象、函数、全局作用域和局部作用域
这篇文章介绍了JavaScript中对象的基本概念和操作,包括对象属性和方法的使用、对象字面量的创建、函数的定义和作用域的概念,以及全局作用域和局部作用域的区别和特性。
JavaScript基础&实战(4)js中的对象、函数、全局作用域和局部作用域
|
1月前
|
JavaScript 前端开发
JavaScript基础&实战(3)js中的流程控制语句、条件分支语句、for循环、while循环
这篇文章讲解了JavaScript中的流程控制语句,包括基本的if条件判断、弹窗提示输入、switch条件分支语句、while和do...while循环以及for循环的使用和示例。
JavaScript基础&实战(3)js中的流程控制语句、条件分支语句、for循环、while循环
|
1月前
|
JavaScript 前端开发
JavaScript基础&实战(2)js中的强制类型转换、运算符、关系运算符、逻辑运算符、条件运算符
这篇文章详细介绍了JavaScript中的强制类型转换、运算符(包括算术、逻辑、条件、赋值和关系运算符)的使用方法和优先级规则。
JavaScript基础&实战(2)js中的强制类型转换、运算符、关系运算符、逻辑运算符、条件运算符
|
20天前
|
Java 数据库连接 数据库
从零到精通:揭秘 Hibernate 构建持久层服务的全过程,你离数据持久化大师还有多远?
【8月更文挑战第31天】本文详细介绍了如何从零开始使用 Hibernate 构建一个持久层服务。首先,通过在 Maven 项目中添加必要的依赖,确保项目具备使用 Hibernate 的条件。接着,配置 `hibernate.cfg.xml` 文件以连接 MySQL 数据库,并设置了基本属性。然后定义了一个简单的 `User` 实体类及其映射关系。此外,还创建了一个 `HibernateUtil` 工具类来管理 `SessionFactory`。
28 0
|
21天前
|
缓存 JavaScript 前端开发
Vue.js与JavaScript性能优化终极揭秘:掌握这些技巧,让你的Web应用飞一般地流畅!
【8月更文挑战第30天】随着前端应用复杂度的增加,性能优化变得至关重要。本文深入探讨了如何利用Vue.js和JavaScript实现高效的应用性能。主要内容包括:优化组件设计以减少不必要的渲染,采用异步组件与懒加载技术加速应用启动,利用虚拟滚动和分页处理大数据集,改进Vuex使用方式以及合理运用浏览器缓存等策略。通过具体示例和最佳实践,帮助开发者充分挖掘Vue.js潜力,打造高性能的前端应用。
33 0
|
21天前
|
设计模式 JavaScript 前端开发
从工厂到单例再到策略:Vue.js高效应用JavaScript设计模式
【8月更文挑战第30天】在现代Web开发中,结合使用JavaScript设计模式与框架如Vue.js能显著提升代码质量和项目的可维护性。本文探讨了常见JavaScript设计模式及其在Vue.js中的应用。通过具体示例介绍了工厂模式、单例模式和策略模式的应用场景及其实现方法。例如,工厂模式通过`NavFactory`根据用户角色动态创建不同的导航栏组件;单例模式则通过全局事件总线`eventBus`实现跨组件通信;策略模式用于处理不同的表单验证规则。这些设计模式的应用不仅提高了代码的复用性和灵活性,还增强了Vue应用的整体质量。
14 0
|
21天前
|
JavaScript 前端开发 API
揭秘Vue.js与JavaScript融合的神秘力量:如何一键解锁高效响应式Web应用的终极秘籍?
【8月更文挑战第30天】随着前端技术的发展,Vue.js凭借其轻量级、易上手和高度响应式的特性,在前端开发领域迅速崛起,成为构建现代Web应用的首选框架之一。Vue.js与JavaScript深度融合,使开发者能高效灵活地打造美观且功能强大的应用。本文将作为实战指南,带您深入了解Vue.js与JavaScript结合的奥秘,揭示构建高效响应式Web应用的秘籍。从Vue.js的基础开始,逐步介绍如何利用其数据驱动视图的特点,结合JavaScript的高级特性,如定时器、Promise、async/await等,提升应用的交互性和用户体验。
10 0