21天筑基期--JavaScript系列
JavaScript、TypeScript和ES6
有重合部分,我认为放一起比较合理
JS 的数据类型有哪些?
纯记忆题,答案有 8 个词,建议背诵 10 次。
字符串、数字、布尔、undefined、null、大整数、符号、对象
string、number、boolean、undefined、null、bigint、symbol、object
提了就零分的答案有:数组、函数、日期。这些是类 class,不是类型 type
ES6常用的API有哪些?
var、let和const的区别
var
声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
let和const:引入了块级作用域变量的声明方式。
const
声明一个只读的常量。一旦声明,常量的值就不能改变
箭头函数:提供了更简洁的函数声明语法。
-
简洁的语法形式:箭头函数使用了更简洁的语法形式,省略了传统函数声明中的
function
关键字和大括号。它通常可以在更少的代码行数中表达相同的逻辑。 -
没有自己的this:箭头函数没有自己的
this
绑定,它会捕获所在上下文的this
值。这意味着箭头函数中的this
与其定义时所在的上下文中的this
保持一致,而不是在函数被调用时动态绑定。这可以避免传统函数中常见的this
指向问题,简化了对this
的使用和理解。 -
没有
arguments
对象:箭头函数也没有自己的arguments
对象。如果需要访问函数的参数,可以使用剩余参数(Rest Parameters)或使用展开运算符(Spread Operator)将参数传递给其他函数。 -
无法作为构造函数:箭头函数不能用作构造函数,不能使用
new
关键字调用。它们没有prototype
属性,因此无法使用new
关键字创建实例。 -
隐式的返回值:如果箭头函数的函数体只有一条表达式,并且不需要额外的处理逻辑,那么可以省略大括号并且该表达式将隐式作为返回值返回。
-
不能绑定自己的this、super、new.target:由于箭头函数没有自己的
this
绑定,也无法使用
模板字符串:允许使用反引号(`)创建多行字符串和插入变量。
解构赋值:可以从数组或对象中快速提取值并赋给变量。
JavaScript 解构赋值实用指南 - 掘金 (juejin.cn)
默认参数:在函数声明时可以设置参数的默认值。
扩展运算符:用于将数组或对象展开为独立的元素。
类(Class) :引入了类和面向对象编程的概念。
模块化(Modules) :通过import和export语句实现模块的导入和导出。
Promise:用于处理异步操作,提供了更优雅的方式来处理回调函数。
-
Promise.all()
:中的Promise序列会全部执行通过才认为是成功,否则认为是失败; -
Promise.race()
:中的Promise序列中第一个执行完毕的是通过,则认为成功,如果第一个执行完毕的Promise是拒绝,则认为失败; -
Promise.any()
:中的Promise序列只要有一个执行通过,则认为成功,如果全部拒绝,则认为失败; -
Promise.allSettled()
:是一个用于处理多个 Promise 对象的方法,并且会返回一个包含每个 Promise 对象的解决状态的数组; -
Promise.resolve()
:会返回一个新的 Promise 实例,该实例的状态为fulfilled
。 -
Promise.reject()
:也会返回一个新的 Promise 实例,该实例的状态为rejected
Set和Map:提供了集合和字典数据结构,分别对应Set和Map对象。
Symbol:引入了一种新的原始数据类型,用于创建唯一的标识符。
Proxy:允许创建一个代理对象,用于拦截和自定义对象的操作。
数组方法
想学Js数据结构与算法,学好数组必不可少! - 掘金 (juejin.cn)
Map 和 forEach 的区别
相同点:
- 都能遍历数组
- 中途不能被break打断
- 函数中都有三个参数,当前遍历的元素,当前元素的索引,原数组。
不同:
- forEach没有返回值,也就是返回undefined,map会开辟新的一个内存空间,返回新的数组,这点也方便链式调用其他数组方法。
- map的效率比forEach高
对象方法
JavaScript 对象解析 - 掘金 (juejin.cn)
ES6引入了一些新的对象方法,如Object.assign、Object.keys、Object.values等。
== 和 ===区别
== 等于操作符 === 全等操作符
区别 相等操作符(==)会做类型转换,再进行值的比较,全等运算符不会做类型转换
除了在比较对象属性为null
或者undefined
的情况下,我们可以使用相等操作符(==),其他情况建议一律使用全等操作符(===)
闭包
闭包是指那些能够访问自由变量的函数,当所有函数被保存到外部时
闭包 = 函数 + 自由变量
任何闭包的使用场景都离不开这两点:
- 创建私有变量
- 延长变量的生命周期
原型链是什么?
- 原型:每一个 JavaScript 对象(null 除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性,其实就是
prototype
对象。 假设我们有一个数组对象a=[]
,这个a
也会有一个隐藏属性,叫做_proto_这个属性会指向Array.prototype
var a = [];
a.__proto__ === Array.prototype;
// 用 b表示 Array.prototype
b._proto_ === Object.prototype
于是就通过隐藏属性 __proto__
形成了一个链条:
a ===> Array.prototype ===> Object.prototype
这就是原型链。 怎么做:
看起来只要改写 b 的隐藏属性 __proto__
就可以改变 b的原型(链)
const x = Object.create(原型)
// 或
const x = new 构造函数() // 会导致 x.__?????__ === 构造函数.prototype
这样一来,a 就既拥有 Array.prototype 里的属性,又拥有 Object.prototype 里的属性。 解决了什么问题:
在没有 Class 的情况下实现「继承」。以 a ===> Array.prototype ===> Object.prototype
为例,我们说:
- a 是 Array 的实例,a 拥有 Array.prototype 里的属性
- Array 继承了 Object(注意专业术语的使用)
- a 是 Object 的间接实例,a 拥有 Object.prototype 里的属性
优点:
简单、优雅。
缺点:
跟 class 相比,不支持私有属性。
怎么解决缺点:
使用 class 呗。但 class 是 ES6 引入的,不被旧 IE 浏览器支持。
建议熟读这篇文章:
JS 中 proto 和 prototype 存在的意义是什么?
JS继承方式
- 原型链继承
- 构造函数继承(借助 call)
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
bind、call、apply 区别
Function.prototype.apply() - JavaScript | MDN (mozilla.org)
call
、apply
、bind
作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this
指向
- 三者都可以改变函数的
this
对象指向 - 三者第一个参数都是
this
要指向的对象,如果如果没有这个参数或参数为undefined
或null
,则默认指向全局window
- 三者都可以传参,但是
apply
是数组,而call
是参数列表,且apply
和call
是一次性传入参数,而bind
可以分为多次传入 bind
是返回绑定this之后的函数,apply
、call
则是立即执行
TS 和 JS 的区别是什么?有什么优势?
- 语法层面:TypeScript = JavaScript + Type(TS 是 JS 的超集)
- 执行环境层面:浏览器、Node.js 可以直接执行 JS,但不能执行 TS(Deno 可以执行 TS)
- 编译层面:TS 有编译阶段,JS 没有编译阶段(只有转译阶段和 lint 阶段)
- 编写层面:TS 更难写一点,但是类型更安全
- 文档层面:TS 的代码写出来就是文档,IDE 可以完美提示。JS 的提示主要靠 TS
TS特性
- 类型批注和编译时类型检查 :在编译时批注变量类型
- 类型推断:ts中没有批注变量类型会自动推断变量的类型
- 类型擦除:在编译过程中批注的内容和接口会在运行时利用工具擦除
- 接口:ts中用接口来定义对象类型
- 枚举:用于取值被限定在一定范围内的场景
- Mixin:可以接受任意类型的值
- 泛型编程:写代码时使用一些以后才指定的类型
- 名字空间:名字只在该区域内有效,其他区域可重复使用该名字而不冲突
- 元组:元组合并了不同类型的对象,相当于一个可以装不同类型数据的数组
typescript 的数据类型有哪些
typescript
的数据类型主要有如下:
- boolean(布尔类型)
- number(数字类型)
- string(字符串类型)
- array(数组类型)
- tuple(元组类型)
- enum(枚举类型)
- any(任意类型)
- null 和 undefined 类型
- void 类型
- never 类型
- object 对象类型
type 和 interface 的区别是什么?
typescript 中的 interface 和 type 到底有什么区别?
- 组合方式:interface 使用 extends 来实现继承,type 使用 & 来实现联合类型。
- 扩展方式:interface 可以重复声明用来扩展,type 一个类型只能声明一次
- 范围不同:type 适用于基本类型,interface 一般不行。
- 命名方式:interface 会创建新的类型名,type 只是创建类型别名,并没有新创建类型。
其他……建议搜一下博客。
前端路由
这个大佬文章清晰 深入浅出前端路由
手写new操作符
那么我们就动手来实现一下new
function mynew(Func, ...args) {
// 1.创建一个新对象
const obj = {}
// 2.新对象原型指向构造函数原型对象
obj.__proto__ = Func.prototype
// 3.将构建函数的this指向新对象
let result = Func.apply(obj, args)
// 4.根据返回值判断
return result instanceof Object ? result : obj
}
手写 AJAX
AJAX(Asynchronous JavaScript and XML),指的是通过 JavaScript 的异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
1.创建XMLHttpRequest
对象,创建一个异步调用对象.
2.创建一个新的HTTP
请求,并指定该HTTP
请求的方法、URL
及验证信息.
3.设置响应HTTP
请求状态变化的函数.
4.发送HTTP
请求
记忆题,写博客吧
const ajax = (method, url, data, success, fail) => {
var request = new XMLHttpRequest()
request.open(method, url);
request.onreadystatechange = function () {
if(request.readyState === 4) {
if(request.status >= 200 && request.status < 300 || request.status === 304) {
success(request)
}else{
fail(request)
}
}
};
request.send();
}
ajax({
method: 'GET',
url: 'https://example.com/api/data',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your_token'
},
data: null, // Optional data to send in the request body
success: function(response) {
console.log('Request successful:', response);
// Handle the successful response
},
error: function(status) {
console.log('Request failed with status:', status);
// Handle the error response
}
});
深拷贝和浅拷贝
浅拷贝
基本类型数据保存在在栈内存中
引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中 下面简单实现一个浅拷贝
function shallowClone(obj) {
const newObj = {};
for(let prop in obj) {
if(obj.hasOwnProperty(prop)){
newObj[prop] = obj[prop];
}
}
return newObj;
}
在JavaScript
中,存在浅拷贝的现象有:
Object.assign
Array.prototype.slice()
,Array.prototype.concat()
- 使用拓展运算符实现的复制
Object.assign
var obj = {
age: 18,
nature: ['smart', 'good'],
names: {
name1: 'fx',
name2: 'xka'
},
love: function () {
console.log('fx is a great girl')
}
}
var newObj = Object.assign({}, fxObj);
slice()
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
concat()
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
拓展运算符
const fxArr = ["One", "Two", "Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
深拷贝
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
- _.cloneDeep()
- jQuery.extend()
- JSON.stringify()
- 手写循环递归
_.cloneDeep()
const _ = require('lodash');
const obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
jQuery.extend()
const $ = require('jquery');
const obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
JSON.stringify()
const obj2=JSON.parse(JSON.stringify(obj1));
但是这种方式存在弊端,会忽略undefined
、symbol
和函数
const obj = {
name: 'A',
name1: undefined,
name3: function() {},
name4: Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}
循环递归
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;
}
防抖和节流
- 节流:只执行第一次点击,在第一次点击完成前,后面的点击都会无效,技能CD
- 防抖:防抖是在多次点击中,只执行最后一次,前面的点击都会被取消,回城
function throttled(fn, delay = 500) {
let timer = null
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay);
}
}
}
function debounce(fun,time){
let timer
return function(...args){
clearTimeout(timer) // 打断回城
// 重新回城
timer=setTimeout(()=>{
fun.apply(this,args) // 回城后调用 fn
},time)
}
}
大文件上传如何做断点续传
上传大文件时,以下几个变量会影响我们的用户体验
- 服务器处理数据的能力
- 请求超时
- 网络波动
web常见的攻击方式
axios封装
axios拦截器分为响应和请求拦截器,请求拦截器 在请求发送前进行必要操作处理,例如添加统一cookie、请求体加验证、设置请求头等,相当于是对每个接口里相同操作的一个封装; 响应拦截器 同理,响应拦截器也是如此功能,只是在请求得到响应之后,对响应体的一些处理,通常是数据统一处理等,也常来判断登录失效等
转载自:https://juejin.cn/post/7252586606090846268