🤪五、this问题
1、描述下this(谈谈对this对象的理解)
this
,函数执行的上下文,总是指向函数的直接调用者(而非间接调用者),可以通过apply
,call
,bind
改变this
的指向。- 如果有
new
关键字,this
指向new
出来的那个对象。 - 在事件中,
this
指向触发这个事件的对象,特殊的是,IE
中的attachEvent
中的this
总是指向全局对象window
。 - 对于匿名函数或者直接调用的函数来说,
this
指向全局上下文(浏览器为window
,NodeJS
为global
),剩下的函数调用,那就是谁调用它,this
就指向谁。 - 对于
es6
的箭头函数,箭头函数的指向取决于该箭头函数声明的位置,在哪里声明,this
就指向哪里。
2、this绑定的四大规则
this绑定四大规则遵循以下顺序:
New 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
下面一一介绍四大规则。
(1)New绑定
- New 绑定:
new
调用函数会创建一个全新的对象,并将这个对象绑定到函数调用的this
。New
绑定时,如果是new
一个硬绑定函数,那么会用new
新建的对象替换这个硬绑定this
。具体实现代码如下:
function foo(a) { this.a = a; } var bar = new foo(2); console.log(bar.a); //2 复制代码
(2)显式绑定
- 显示绑定:通过在函数上运行
call
和apply
,来显示绑定的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)默认绑定
- 默认绑定:没有其他修饰(
bind
、apply
、call
),在非严格模式下定义指向全局对象,在严格模式下定义指向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)三者的区别
apply
、call
、bind
三者都是函数的方法,都可以改变函数的this
指向。apply
和call
都是改变函数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
的过程只涉及Javascript
、XMLHttpRequest
和DOM
,其中XMLHttpRequest
是ajax
的核心机制。
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 渲染完即可执行,此时图片、视频还可能没加载完 -> 尽量选择此方法 }); 复制代码
- 关于 DOM 和 BOM 操作的补充👇
- 原文:提升对前端的认知,不得不了解Web API的DOM和BOM
- 链接:juejin.cn/post/697156…
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
响应的状态码是404
或500
。仅当网络故障或请求被阻止时,才会标记为reject
。- 只对网络请求报错,对
400
,500
都当做成功的请求,需要封装去处理。 - 这里对于
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)手写防抖函数
防抖:一段时间内连续触发,不执行,直到超出限定时间执行最后一次。—— 如: input
、 scroll
滚动
通俗来讲,防抖是从频繁触发执行变为最后一次才执行。
//封装防抖函数,实现防抖 function denounce(func, delay=500){ let timer = null; return function(...args){ // 如果有值,清除定时器,之后继续执行 if(timer){ clearTimeout(timer); } timer = setTimeout(() => { func.apply(this, args); },delay); } } 复制代码
- 详解文章补充(防抖节流)👇
- 原文:关于前端性能优化问题,认识网页加载过程和防抖节流
- 链接:juejin.cn/post/697306…
(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++; } } } } 复制代码
- 详细文章补充👇
- 原文:原生js实现图片懒加载(lazyLoad)
- 链接:zhuanlan.zhihu.com/p/55311726
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
~