作者:Mark A
译者:前端小智
起源:dev
点赞再看,微信搜寻 【大迁世界】 关注这个没有大厂背景,但有着一股向上踊跃心态人。本文
GitHub
https://github.com/qq44924588… 上曾经收录,文章的已分类,也整顿了很多我的文档,和教程材料。
编程, 建网站必备的阿里云服务器居然免费送了!
- 51. 什么是
async/await
及其如何工作? - 52. 开展运算符和 Rest 运算符有什么区别?
- 53. 什么是默认参数?
- 54. 什么是包装对象(wrapper object)?
- 55. 隐式和显式转换有什么区别?
- 56. 什么是 NaN?以及如何查看值是否为 NaN?
- 57. 如何判断值是否为数组?
- 58. 如何在不应用
%
模运算符的状况下查看一个数字是否是偶数? - 59. 如何查看对象中是否存在某个属性?
- 60. AJAX 是什么?
- 61. 如何在 JavaScript 中创建对象?
- 62. Object.seal 和 Object.freeze 办法之间有什么区别?
- 63. 对象中的 in 运算符和 hasOwnProperty 办法有什么区别?
- 64. 有哪些办法能够解决 javascript 中的异步代码?
- 65. 函数表达式和函数申明之间有什么区别?
- 66. 调用函数,能够应用哪些办法?
- 67. 什么是缓存及它有什么作用?
- 68. 手动实现缓存办法
- 69. 为什么 typeof null 返回 object?如何查看一个值是否为 null?
- 70. new 关键字有什么作用?
- 71. 什么时候不应用箭头函数? 说出三个或更多的例子?
- 72. Object.freeze() 和 const 的区别是什么?
- 73. 如何在 JS 中“深解冻”对象?
- 74.
Iterator
是什么,有什么作用? - 75.
Generator
函数是什么,有什么作用?
51. 什么是 async/await
及其如何工作?
async/await
是 JS 中编写异步或非阻塞代码的新办法。它建设在 Promises 之上,让异步代码的可读性和简洁度都更高。
async/await
是 JS 中编写异步或非阻塞代码的新办法。它建设在 Promises
之上,绝对于 Promise 和回调,它的可读性和简洁度都更高。然而,在应用此性能之前,咱们必须先学习 Promises
的基础知识,因为正如我之前所说,它是基于 Promise
构建的,这意味着幕后应用依然是Promise。
应用 Promise
function callApi() {return fetch("url/to/api/endpoint")
.then(resp => resp.json())
.then(data => {//do something with "data"}).catch(err => {//do something with "err"});
}
应用 async/await
在async/await
,咱们应用 tru/catch 语法来捕捉异样。
async function callApi() {
try {const resp = await fetch("url/to/api/endpoint");
const data = await resp.json();
//do something with "data"
} catch (e) {//do something with "err"}
}
留神 : 应用 async
要害申明函数会隐式返回一个Promise。
const giveMeOne = async () => 1;
giveMeOne()
.then((num) => {console.log(num); // logs 1
});
留神:await
关键字只能在 async function
中应用。在任何非 async function 的函数中应用 await
关键字都会抛出谬误。await
关键字在执行下一行代码之前期待右侧表达式 (可能是一个Promise) 返回。
const giveMeOne = async () => 1;
function getOne() {
try {const num = await giveMeOne();
console.log(num);
} catch (e) {console.log(e);
}
}
// Uncaught SyntaxError: await is only valid in async function
async function getTwo() {
try {const num1 = await giveMeOne(); // 这行会期待右侧表达式执行实现
const num2 = await giveMeOne();
return num1 + num2;
} catch (e) {console.log(e);
}
}
await getTwo(); // 2
52. 开展 (spread) 运算符和 残余(Rest) 运算符有什么区别?
开展运算符 (spread) 是三个点(...
),能够将一个数组转为用逗号分隔的参数序列。说的通俗易懂点,有点像化骨绵掌,把一个大元素给打散成一个个独自的小元素。
残余运算符也是用三个点 (...
) 示意,它的样子看起来和开展操作符一样,然而它是用于解构数组和对象。在某种程度上,残余元素和开展元素相同,开展元素会“开展”数组变成多个元素,残余元素会收集多个元素和“压缩”成一个繁多的元素。
function add(a, b) {return a + b;};
const nums = [5, 6];
const sum = add(...nums);
console.log(sum);
在本例中,咱们在调用 add
函数时应用了开展操作符,对 nums
数组进行开展。所以参数 a
的值是 5
,b
的值是6
,所以sum
是11
。
function add(...rest) {return rest.reduce((total,current) => total + current);
};
console.log(add(1, 2)); // 3
console.log(add(1, 2, 3, 4, 5)); // 15
在本例中,咱们有一个 add
函数,它承受任意数量的参数,并将它们全副相加,而后返回总数。
const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(others); // [2,3,4,5]
这里,咱们应用残余操作符提取所有残余的数组值,并将它们放入除第一项之外的其余数组中。
53. 什么是默认参数?
默认参数是在 JS 中定义默认变量的一种新办法,它在 ES6 或 ECMAScript 2015 版本中可用。
//ES5 Version
function add(a,b){
a = a || 0;
b = b || 0;
return a + b;
}
//ES6 Version
function add(a = 0, b = 0){return a + b;}
add(1); // returns 1
咱们还能够在默认参数中应用解构。
function getFirst([first, ...rest] = [0, 1]) {return first;}
getFirst(); // 0
getFirst([10,20,30]); // 10
function getArr({nums} = {nums: [1, 2, 3, 4] }){return nums;}
getArr(); // [1, 2, 3, 4]
getArr({nums:[5,4,3,2,1]}); // [5,4,3,2,1]
咱们还能够应用先定义的参数再定义它们之后的参数。
function doSomethingWithValue(value = "Hello World", callback = () => {console.log(value) }) {callback();
}
doSomethingWithValue(); //"Hello World"
54. 什么是包装对象(wrapper object)?
咱们当初温习一下 JS 的数据类型,JS 数据类型被分为两大类,根本类型 和援用类型。
根本类型:Undefined
,Null
,Boolean
,Number
,String
,Symbol
,BigInt
援用类型:Object
,Array
,Date
,RegExp
等,说白了就是对象。
其中援用类型有办法和属性,然而根本类型是没有的,但咱们常常会看到上面的代码:
let name = "marko";
console.log(typeof name); // "string"
console.log(name.toUpperCase()); // "MARKO"
name
类型是 string
,属于根本类型,所以它没有属性和办法,然而在这个例子中,咱们调用了一个 toUpperCase()
办法,它不会抛出谬误,还返回了对象的变量值。
起因是根本类型的值被长期转换或强制转换为 对象 ,因而name
变量的行为相似于 对象 。除null
和undefined
之外的每个根本类型都有本人 包装对象 。也就是:String
,Number
,Boolean
,Symbol
和BigInt
。在这种状况下,name.toUpperCase()
在幕后看起来如下:
console.log(new String(name).toUpperCase()); // "MARKO"
在实现拜访属性或调用办法之后,新创建的对象将立刻被抛弃。
55. 隐式和显式转换有什么区别)?
隐式强制转换是一种将值转换为另一种类型的办法,这个过程是主动实现的,无需咱们手动操作。
假如咱们上面有一个例子。
console.log(1 + '6'); // 16
console.log(false + true); // 1
console.log(6 * '2'); // 12
第一个 console.log
语句后果为 16
。在其余语言中,这会抛出编译时谬误,但在 JS 中,1
被转换成字符串,而后与 + 运
算符连贯。咱们没有做任何事件,它是由 JS 主动实现。
第二个 console.log
语句后果为 1
,JS 将false
转换为 boolean
值为 0
,,true
为1
,因而后果为1
。
第三个 console.log
语句后果 12
,它将'2'
转换为一个数字,而后乘以6 * 2
,后果是 12。
而显式强制是将值转换为另一种类型的办法,咱们须要手动转换。
console.log(1 + parseInt('6'));
在本例中,咱们应用 parseInt
函数将 '6'
转换为 number
,而后应用+
运算符将 1
和6
相加。
56. 什么是 NaN?以及如何查看值是否为 NaN?
NaN
示意 “非数字” 是 JS 中的一个值,该值是将数字转换或执行为非数字值的运算后果,因而后果为NaN
。
let a;
console.log(parseInt('abc')); // NaN
console.log(parseInt(null)); // NaN
console.log(parseInt(undefined)); // NaN
console.log(parseInt(++a)); // NaN
console.log(parseInt({} * 10)); // NaN
console.log(parseInt('abc' - 2)); // NaN
console.log(parseInt(0 / 0)); // NaN
console.log(parseInt('10a' * 10)); // NaN
JS 有一个内置的 isNaN
办法,用于测试值是否为 isNaN 值,然而这个函数有一个奇怪的行为。
console.log(isNaN()); // true
console.log(isNaN(undefined)); // true
console.log(isNaN({})); // true
console.log(isNaN(String('a'))); // true
console.log(isNaN(() => {})); // true
所有这些 console.log
语句都返回true
,即便咱们传递的值不是NaN
。
在 ES6
中,倡议应用 Number.isNaN
办法,因为它的确会查看该值(如果的确是 NaN
),或者咱们能够使本人的辅助函数查看此问题,因为在 JS 中,NaN 是惟一的值,它不等于本人。
function checkIfNaN(value) {return value !== value;}
57. 如何判断值是否为数组?
咱们能够应用 Array.isArray
办法来查看值是否为 数组。当传递给它的参数是数组时,它返回true
,否则返回false
。
console.log(Array.isArray(5)); // false
console.log(Array.isArray("")); // false
console.log(Array.isArray()); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray({ length: 5})); // false
console.log(Array.isArray([])); // true
如果环境不反对此办法,则能够应用 polyfill
实现。
function isArray(value){return Object.prototype.toString.call(value) === "[object Array]"
}
当然还能够应用传统的办法:
let a = []
if (a instanceof Array) {console.log('是数组')
} else {console.log('非数组')
}
58. 如何在不应用 %
模运算符的状况下查看一个数字是否是偶数?
咱们能够对这个问题应用按位 &
运算符,&
对其操作数进行运算,并将其视为二进制值,而后执行与运算。
function isEven(num) {if (num & 1) {return false} else {return true}
}
0
二进制数是 000
1
二进制数是 001
2
二进制数是 010
3
二进制数是 011
4
二进制数是 100
5
二进制数是 101
6
二进制数是 110
7
二进制数是 111
以此类推 …
与运算的规定如下:
a | b | a & b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 1 | 1 |
因而,当咱们执行 console.log(5&1)
这个表达式时,后果为 1
。首先,&
运算符将两个数字都转换为二进制,因而 5
变为 101
,1
变为001
。
而后,它应用按位怀运算符比拟每个位(0
和 1
)。101&001
,从表中能够看出,如果a & b
为1
,所以 5&1
后果为1
。
101 & 001 |
---|
101 |
001 |
001 |
- 首先咱们比拟最右边的
1&0
,后果是0
。 - 而后咱们比拟两头的
0&0
,后果是0
。 - 而后咱们比拟最初
1&1
,后果是1
。 -
最初,失去一个二进制数
001
,对应的十进制数,即1
。由此咱们也能够算出
console.log(4 & 1)
后果为0
。晓得4
的最初一位是0
,而0 & 1
将是0
。如果你很难了解这一点,咱们能够应用递归函数来解决此问题。function isEven(num) {
if (num < 0 || num === 1) return false; if (num == 0) return true; return isEven(num - 2);
}
59. 如何查看对象中是否存在某个属性?
查看对象中是否存在属性有三种办法。
第一种应用 in
操作符号:
const o = {
"prop" : "bwahahah",
"prop2" : "hweasa"
};
console.log("prop" in o); // true
console.log("prop1" in o); // false
第二种应用 hasOwnProperty
办法,hasOwnProperty()
办法会返回一个布尔值,批示对象本身属性中是否具备指定的属性(也就是,是否有指定的键)。
console.log(o.hasOwnProperty("prop2")); // true
console.log(o.hasOwnProperty("prop1")); // false
第三种应用括号符号obj["prop"]
。如果属性存在,它将返回该属性的值,否则将返回undefined
。
console.log(o["prop"]); // "bwahahah"
console.log(o["prop1"]); // undefined
60. AJAX 是什么?
即异步的 JavaScript 和 XML,是一种用于创立疾速动静网页的技术,传统的网页(不应用 AJAX)如果须要更新内容,必须重载整个网页面。应用 AJAX 则不须要加载更新整个网页,实现局部内容更新
用到 AJAX 的技术:
- HTML – 网页构造
- CSS – 网页的款式
- JavaScript – 操作网页的行为和更新 DOM
- XMLHttpRequest API – 用于从服务器发送和获取数据
- PHP,Python,Nodejs – 某些服务器端语言
61. 如何在 JS 中创建对象?
应用对象字面量:
const o = {
name: "前端小智",
greeting() {return `Hi, 我是 ${this.name}`;
}
};
o.greeting(); // "Hi, 我是前端小智"
应用构造函数:
function Person(name) {this.name = name;}
Person.prototype.greeting = function () {return `Hi, 我是 ${this.name}`;
}
const mark = new Person("前端小智");
mark.greeting(); // "Hi, 我是前端小智"
应用 Object.create 办法:
const n = {greeting() {return `Hi, 我是 ${this.name}`;
}
};
const o = Object.create(n);
o.name = "前端小智";
62. Object.seal 和 Object.freeze 办法之间有什么区别?
Object.freeze()
Object.freeze()
办法能够解冻一个对象。一个被解冻的对象再也不能被批改;解冻了一个对象则不能向这个对象增加新的属性,不能删除已有属性,不能批改该对象已有属性的可枚举性、可配置性、可写性,以及不能批改已有属性的值。此外,解冻一个对象后该对象的原型也不能被批改。freeze()
返回和传入的参数雷同的对象。
Object.seal()
Object.seal()办法关闭一个对象,阻止增加新属性并将所有现有属性标记为不可配置。以后属性的值只有可写就能够扭转。
办法的相同点:
- ES5 新增。
- 对象不可能扩大,也就是不能再增加新的属性或者办法。
- 对象已有属性不容许被删除。
- 对象属性个性不能够重新配置。
办法不同点:
Object.seal
办法生成的密封对象,如果属性是可写的,那么能够批改属性值。
* Object.freeze
办法生成的解冻对象,属性都是不可写的,也就是属性值无奈更改。
63. in
运算符和 Object.hasOwnProperty
办法有什么区别?
hasOwnPropert 办法
hasOwnPropert()
办法返回值是一个布尔值,批示对象本身属性中是否具备指定的属性,因而这个办法会疏忽掉那些从原型链上继承到的属性。
看上面的例子:
Object.prototype.phone= '15345025546';
let obj = {
name: '前端小智',
age: '28'
}
console.log(obj.hasOwnProperty('phone')) // false
console.log(obj.hasOwnProperty('name')) // true
能够看到,如果在函数原型上定义一个变量 phone
,hasOwnProperty
办法会间接疏忽掉。
in 运算符
如果指定的属性在指定的对象或其原型链中,则in
运算符返回true
。
还是用下面的例子来演示:
console.log('phone' in obj) // true
能够看到 in
运算符会查看它或者其原型链是否蕴含具备指定名称的属性。
64. 有哪些办法能够解决 JS 中的异步代码?
- 回调
- Promise
- async/await
- 还有一些库:async.js, bluebird, q, co
65. 函数表达式和函数申明之间有什么区别?
看上面的例子:
hoistedFunc();
notHoistedFunc();
function hoistedFunc(){console.log("留神:我会被晋升");
}
var notHoistedFunc = function(){console.log("留神:我没有被晋升");
}
notHoistedFunc
调用抛出异样:Uncaught TypeError: notHoistedFunc is not a function
,而 hoistedFunc
调用不会,因为 hoistedFunc
会被晋升到作用域的顶部,而notHoistedFunc
不会。
66. 调用函数,能够应用哪些办法?
在 JS 中有 4 种办法能够调用函数。
作为函数调用——如果一个函数没有作为办法、构造函数、apply
、call
调用时,此时 this
指向的是 window
对象(非严格模式)
//Global Scope
function add(a,b){console.log(this);
return a + b;
}
add(1,5); // 打印 "window" 对象和 6
const o = {method(callback){callback();
}
}
o.method(function (){console.log(this); // 打印 "window" 对象
});
作为办法调用 ——如果一个对象的属性有一个函数的值,咱们就称它为 办法 。调用该办法时,该办法的this
值指向该对象。
const details = {
name : "Marko",
getName(){return this.name;}
}
details.getName(); // Marko
作为构造函数的调用 - 如果在函数之前应用new
关键字调用了函数,则该函数称为 构造函数
。构造函数外面会默认创立一个空对象,并将this
指向该对象。
function Employee(name, position, yearHired) {// 创立一个空对象 {}
// 而后将空对象调配给“this”关键字
// this = {};
this.name = name;
this.position = position;
this.yearHired = yearHired;
// 如果没有指定 return , 这里会默认返回 this
};
const emp = new Employee("Marko Polo", "Software Developer", 2017);
应用 apply
和call
办法调用 ——如果咱们想显式地指定一个函数的this
值,咱们能够应用这些办法,这些办法对所有函数都可用。
const obj1 = {result:0};
const obj2 = {result:0};
function reduceAdd(){
let result = 0;
for(let i = 0, len = arguments.length; i < len; i++){result += arguments[i];
}
this.result = result;
}
reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // reduceAdd 函数中的 this 对象将是 obj1
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // reduceAdd 函数中的 this 对象将是 obj2
67. 什么是缓存及它有什么作用?
缓存是建设一个函数的过程,这个函数可能记住之前计算的后果或值。应用缓存函数是为了防止在最初一次应用雷同参数的计算中曾经执行的函数的计算。这节俭了工夫,但也有不利的一面,即咱们将耗费更多的内存来保留以前的后果。
68. 手动实现缓存办法]
function memoize(fn) {const cache = {};
return function (param) {if (cache[param]) {console.log('cached');
return cache[param];
} else {let result = fn(param);
cache[param] = result;
console.log(`not cached`);
return result;
}
}
}
const toUpper = (str ="")=> str.toUpperCase();
const toUpperMemoized = memoize(toUpper);
toUpperMemoized("abcdef");
toUpperMemoized("abcdef");
这个缓存函数实用于承受一个参数。咱们须要扭转下,让它承受多个参数。
const slice = Array.prototype.slice;
function memoize(fn) {const cache = {};
return (...args) => {const params = slice.call(args);
console.log(params);
if (cache[params]) {console.log('cached');
return cache[params];
} else {let result = fn(...args);
cache[params] = result;
console.log(`not cached`);
return result;
}
}
}
const makeFullName = (fName, lName) => `${fName} ${lName}`;
const reduceAdd = (numbers, startingValue = 0) => numbers.reduce((total, cur) => total + cur, startingValue);
const memoizedMakeFullName = memoize(makeFullName);
const memoizedReduceAdd = memoize(reduceAdd);
memoizedMakeFullName("Marko", "Polo");
memoizedMakeFullName("Marko", "Polo");
memoizedReduceAdd([1, 2, 3, 4, 5], 5);
memoizedReduceAdd([1, 2, 3, 4, 5], 5);
69. 为什么 typeof null 返回 object?如何查看一个值是否为 null?
typeof null == 'object'
总是返回 true
,因为这是自 JS 诞生以来null
的实现。已经有人提出将 typeof null == 'object'
批改为typeof null == 'null'
,然而被回绝了,因为这将导致更多的bug。
咱们能够应用严格相等运算符 ===
来查看值是否为null
。
function isNull(value){return value === null;}
70. new 关键字有什么作用?
new
关键字与构造函数一起应用以创建对象:
function Employee(name, position, yearHired) {
this.name = name;
this.position = position;
this.yearHired = yearHired;
};
const emp = new Employee("Marko Polo", "Software Developer", 2017);
new
关键字做了 4
件事:
- 创立空对象
{}
- 将空对象调配给
this
值 - 将空对象的
__proto__
指向构造函数的prototype
- 如果没有应用显式
return
语句,则返回this
看上面事例:
function Person() {
this.name = ‘ 前端小智 ’
}
依据下面形容的,new Person()
做了:
- 创立一个空对象:
var obj = {}
- 将空对象调配给
this
值:this = obj - 将空对象的
__proto__
指向构造函数的prototype
:this.__proto__ = Person().prototype
- 返回
this
:return this
71. 什么时候不应用箭头函数? 说出三个或更多的例子?
不应该应用箭头函数一些状况:
- 当想要函数被晋升时(箭头函数是匿名的)
- 要在函数中应用
this/arguments
时,因为箭头函数自身不具备this/arguments
,因而它们取决于内部上下文 - 应用命名函数(箭头函数是匿名的)
- 应用函数作为构造函数时(箭头函数没有构造函数)
- 当想在对象字面是以将函数作为属性增加并在其中应用对象时,因为咱们无法访问
this
即对象自身。
72. Object.freeze() 和 const 的区别是什么?]
const
和 Object.freeze
是两个齐全不同的概念。
const
申明一个只读的变量,一旦申明,常量的值就不可扭转:
const person = {name: "Leonardo"};
let animal = {species: "snake"};
person = animal; // ERROR "person" is read-only
Object.freeze
实用于值,更具体地说,实用于对象值,它使对象不可变,即不能更改其属性。
let person = {name: "Leonardo"};
let animal = {species: "snake"};
Object.freeze(person);
person.name = "Lima"; //TypeError: Cannot assign to read only property 'name' of object
console.log(person);
73. 如何在 JS 中“深解冻”对象?
如果咱们想要确保对象被深解冻,就必须创立一个递归函数来解冻对象类型的每个属性:
没有深解冻
let person = {
name: "Leonardo",
profession: {name: "developer"}
};
Object.freeze(person);
person.profession.name = "doctor";
console.log(person); //output {name: 'Leonardo', profession: { name: 'doctor'} }
深解冻
function deepFreeze(object) {let propNames = Object.getOwnPropertyNames(object);
for (let name of propNames) {let value = object[name];
object[name] = value && typeof value === "object" ?
deepFreeze(value) : value;
}
return Object.freeze(object);
}
let person = {
name: "Leonardo",
profession: {name: "developer"}
};
deepFreeze(person);
person.profession.name = "doctor"; // TypeError: Cannot assign to read only property 'name' of object
74. Iterator
是什么,有什么作用?
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供对立的拜访机制。任何数据结构只有部署 Iterator 接口,就能够实现遍历操作(即顺次解决该数据结构的所有成员)。
Iterator
的作用有三个:
- 为各种数据结构,提供一个对立的、简便的拜访接口;
- 使得数据结构的成员可能按某种秩序排列;
- ES6 发明了一种新的遍历命令
for...of
循环,Iterator 接口次要供for...of
生产。
遍历过程:
- 创立一个指针对象,指向以后数据结构的起始地位。也就是说,遍历器对象实质上,就是一个指针对象。
- 第一次调用指针对象的 next 办法,能够将指针指向数据结构的第一个成员。
- 第二次调用指针对象的 next 办法,指针就指向数据结构的第二个成员。
- 一直调用指针对象的 next 办法,直到它指向数据结构的完结地位。
每一次调用 next
办法,都会返回数据结构的以后成员的信息。具体来说,就是返回一个蕴含 value
和done
两个属性的对象。其中,value
属性是以后成员的值,done
属性是一个布尔值,示意遍历是否完结。
//obj 就是可遍历的,因为它遵循了 Iterator 规范,且蕴含 [Symbol.iterator] 办法,办法函数也符合标准的 Iterator 接口标准。//obj.[Symbol.iterator]() 就是 Iterator 遍历器
let obj = {data: [ 'hello', 'world'],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {next() {if (index < self.data.length) {
return {value: self.data[index++],
done: false
};
} else {return { value: undefined, done: true};
}
}
};
}
};
75. Generator
函数是什么,有什么作用?
如果说 JavaScrip 是 ECMAScript 规范的一种具体实现、Iterator
遍历器是 Iterator
的具体实现,那么 Generator
函数能够说是 Iterator
接口的具体实现形式。
执行 Generator
函数会返回一个遍历器对象,每一次 Generator
函数外面的 yield 都相当一次遍历器对象的 next()
办法,并且能够通过 next(value)
办法传入自定义的 value
, 来扭转Generator
函数的行为。
Generator
函数能够通过配合 Thunk 函数更轻松更优雅的实现异步编程和控制流治理。
代码部署后可能存在的 BUG 没法实时晓得,预先为了解决这些 BUG,花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。
原文:
https://dev.to/macmacky/70-ja…
交换
文章每周继续更新,能够微信搜寻「大迁世界」第一工夫浏览和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq44924588… 曾经收录,整顿了很多我的文档,欢送 Star 和欠缺,大家面试能够参照考点温习,另外关注公众号,后盾回复福利,即可看到福利,你懂的。