前言
最近发现自己许多JavaScript基础知识掌握不牢固。趁空闲时间,整理一下JavaScript的基础知识。
正题
数据类型
JavaScript一共有8中数据类型,其中7中是基本数据类型:undefined、null、Boolean、Number、String、Symbol、BigInt(es10新增)。还有一种引用类型Object(其中包括Function、Date、Array等)。 基本数据类型在内存中直接存储其值 引用数据类型在内存中存储的是其地址指针
js的内置对象
全局的对象( global objects )或称标准内置对象,不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在
全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。
标准内置对象的分类
(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。
例如 Infinity、NaN、undefined、null 字面量
(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。
例如 eval()、parseFloat()、parseInt() 等
(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。
例如 Object、Function、Boolean、Symbol、Error 等
(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。
例如 Number、Math、Date
(5)字符串,用来表示和操作字符串的对象。
例如 String、RegExp
(6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array
(7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。
例如 Map、Set、WeakMap、WeakSet
(8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。
例如 SIMD 等
(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。
例如 JSON 等
(10)控制抽象对象
例如 Promise、Generator 等
(11)反射
例如 Reflect、Proxy
(12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。
例如 Intl、Intl.Collator 等
(13)WebAssembly
(14)其他
truly 和 falsely变量
被Boolean(变量) === true 的变量 成为truly变量; 被Boolean(变量) === false 的变量 成为falsely变量;
那应该如何区分truly变量和falsely变量呢?
以下都是falsely变量,除此之外都是truly变量:
- Boolean(0) === false
- Boolean(NaN) === false
- Boolean('') === false
- Boolean(null) === false
- Boolean(undefined) === false
- Boolean(false) === false
所以Boolean({}) === true; !!{} === true 所以Boolean([]) === true;
箭头函数
- 箭头函数的this永远指向父级作用域的this。不是调用时的this。普通函数在调用是确定this,指向调用他的那个对象。
- 箭头函数不能作为构造函数。不能使用new()。一方面因为this指向问题。还有一方面箭头函数没有prototype
- 箭头函数没有arguments,caller,callee
- 箭头函数通过call和apply调用时,不会改变this指向。只会传入参数
- 箭头函数没有prototype
寄生式组合继承
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的形式来继承方法。
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log("My name is " + this.name + ".");
};
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.sayMyGrade = function() {
console.log("My grade is " + this.grade + ".");
};
ES6模块和CommonJS模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。CommonJS 模块就是对象,即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段才会生成。
深拷贝(考虑日期、正则等特殊对象解决循环引用)
function deepClone(obj, hash = new WeakMap()) {
if (obj == null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
深拷贝我们平常工作中经常会使用JSON.parse(JSON.stringfy(obj)),但是使用这个方法会一些问题:
- 会忽略值为undefined的字段
- 会忽略值为symbol类型的字段
- 会忽略值为函数的字段
- 不能解决循环引用的对象,会报错
防抖函数
function debounce(func, wait = 1000){
let timer = null
return function(...params){
clearTimeout(timer)
timer = setTimeout(()=>{
timer = null
func.call(this, ...params)
},wait)
}
}
const a = debounce(function(flag){
console.log(flag)
},1000)
a(1)
a(2)
a(3)
a(4)
节流函数
使用Date().now实现
function throttleWithDate(func, wait = 1000, immediate){
let limited = !immediate; // 节流阀标志位
let timer = null;
let start = Date.now();
return function (...args) {
const current = Date.now();
limited = limited && current - start < wait;
if (limited) {
clearTimeout(timer);
timer = setTimeout(() => {
limited = true;
func.apply(this, args);
start = Date.now();
}, wait);
}else {
limited = true;
func.apply(this, args);
start = Date.now();
}
};
}
使用setTimeout实现
function throttleWithTimeout(func, wait = 1000, immediate){
let timer = null
return function (...params){
if(timer){ return }
if(immediate){
func.apply(this, params)
immediate = false
}else{
timer = setTimeout(()=>{
timer = null
immediate = false
func.apply(this, params)
}, wait)
}
}
}
使用reduce实现数组扁平化
const arr = [123,[123,12432,[12321,1232123,12321421,[1232123],12321,[12321],[12321]],12321],1232123]
function myFlat (arr){
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? myFlat(cur) : cur);
}, []);
}
console.log(myFlat(arr))
// [123, 123, 12432, 12321, 1232123, 12321421, 1232123, 12321, 12321, 12321, 12321, 1232123]
实现call
Function.prototype._call = function (context, ...args){
// null,undefined,和不传时,context为 window
context = context == null ? window : context;
// 必须保证 context 是一个对象类型
let contextType = typeof context;
if (!/^(object|function)$/i.test(contextType)) {
// context = new context.constructor(context); // 不适用于 Symbol/BigInt
context = Object(context);
}
let result;
context['fn'] = this; // 把函数作为对象的某个成员值
result = context['fn'](...args); // 把函数执行,此时函数中的this就是context
delete context['fn']; // 设置完成员属性后,删除
return result;
}
实现apply
Function.prototype._apply = function (context, args){
// null,undefined,和不传时,context为 window
context = context == null ? window : context;
// 必须保证 context 是一个对象类型
let contextType = typeof context;
if (!/^(object|function)$/i.test(contextType)) {
// context = new context.constructor(context); // 不适用于 Symbol/BigInt
context = Object(context);
}
let result;
context['fn'] = this; // 把函数作为对象的某个成员值
result = context['fn'](...args); // 把函数执行,此时函数中的this就是context
delete context['fn']; // 设置完成员属性后,删除
return result;
}
实现bind
Function.prototype._bind = function(context, ...params){
if(typeof this !== 'function'){
throw new Error('绑定必须是函数')
}
const self = this
// 此处声明一个func 用于判断 绑定的函数是否作为构造函数new调用
const func = function(){}
const resFunction = function(...args){
// 如果绑定函数作为构造函数new 一个对象时。
// new 会先创建一个新的空对象obj
// 再将obj.__proto__ === 构造函数的(此时就是绑定函数resFunction).prototype
// 那么由于倒数第二行代码返回的绑定函数resFunction 是func的实例
// 所以。当绑定函数作为构造函数 new 一个对象时, this 一定是 func的实例。
// 为了使实例化的对象this指向正确。这时的this就是生成的空对象obj
// 而当绑定函数自己执行的时候。
// this 指向的是window。则使用我们绑定context。
return self.apply(this instanceof func? this: context, params.concat(...args))
}
if(this.prototype){
func.prototype = this.prototype
}
resFunction.prototype = new func()
return resFunction
}
function func(name) {
this.name = name;
}
var obj = {};
var func1 = func._bind(obj);
func1('Jack');
console.log(obj.name); // Jack
var obj1 = new func1('Alice');
console.log(obj.name); // Jack
console.log(obj1.name); // Alice
实现map
Array.prototype._map = function(callback, context){
const arr = this;
const res = []
for(let i = 0; i< arr.length; i++){
res.push(callback.call(context, arr[i],i ,arr))
}
return res
}
这里有一个有趣的面试题
// 返回什么?
['100','200','300','400'].map(Number)
// 返回什么?
['100','200','300','400'].map(parseInt)
// 为什么呢?
实现filter
Array.prototype._filter = function(callback, context){
const arr = this;
const res = []
for(let i = 0 ; i< arr.length; i++){
if(callback.call(context,arr[i], i ,arr)){
res.push(arr[i])
}
}
return res
}
实现reduce
Array.prototype._reduce = function(callback, inital){
const arr = this;
let prev = inital
let initalKey= 0
if(!inital){
for(let i = 0;i<arr.length;i++){
if(arr[i]){
initalKey = i
prev = arr[i]
break
}
}
}
for(let i = initalKey; i< arr.length; i++){
prev = callback.call(undefined, prev, arr[i], i, arr)
}
return prev
}
实现promise.all
Promise._all = function(promiseList){
return new Promise((resolve, reject) => {
let flag = 0
const result = []
promiseList.forEach(promise => {
promise.then(res => {
result.push(res)
flag++
if(flag === promiseList.length){
resolve(result)
}
}).catch(err => {
reject(err)
})
})
})
}
这里有一个有趣的面试题 要求手写一个并发数为3的promise.all
Promise._allWithLimit3 = function(promiseList){
return new Promise((resolve, reject)=>{
const len = promiseList.length
const taskList = promiseList.splice(0,3)
const otherList = promiseList.splice(0)
const result = []
let total = 0;
taskList.forEach(promise=>{
singleTaskRun(promise)
})
function singleTaskRun (promise){
promise.then(res=>{
result.push(res)
total++
if(otherList.length > 0){
const task = otherList.shift()
singleTaskRun(task)
}
if(total === len){
resolve(result)
}
}).catch(err=>{
reject(err)
})
}
})
}
// 测试代码
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("1");
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("2");
}, 1500);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("3");
}, 2000);
});
let p4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("4");
}, 2500);
});
let p5 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("5");
}, 5000);
});
let all = Promise._allWithLimit3([p1, p3, p2, p4, p5]);
all.then(
data => {
console.log("data", data);
}
).catch(err => {
console.log('err',err)
})
关注公众号
喜欢的同学可以关注公众号