写在前面
在前端里,JS是灵魂,是一切逻辑的基础,是前端工程师的根本技术,学好JS才能在之后的框架学习和底层学习中不再泛泛而谈,文本详细介绍了JS的部分基础知识。
食用对象:初级前端
美味指数:😋😋😋😋😋
1.JS的数据类型有哪些?⭐⭐⭐⭐⭐
值类型都是存在栈中,引用类型的值存在堆中,栈中存的变量和内存地址,内存地址指向堆中的值,
这样存储是因为性能问题
基本数据类型:
- Number
- String
- Boolean
- Null
- Undefined
- Symbol(ES6新增数据类型)
- bigInt 引用数据类型:
- Object
- Array
- Date
- Function(特殊, 但不用于存储数据,所以没有“拷贝,复制函数”这一说)
- RegExp
2.在js里怎么判断数据类型?⭐⭐⭐⭐
typeof:
1.能判断所有值类型(string, number, boolean, undefined, Symbol)
2.能判断函数
3.能识别引用类型(不能再继续识别)
instanceof:
1.判断对象对否处于目标对象的原型链上
2.一般用来区分引用类型,不能判断字面量的基本数据类型
Object.prototype.toString.call()
可以区分具体的基本类型和引用类型
console.log(Object.prototype.toString.call("kerwin")); // "[object String]"
var arr = [1, 2, 3];
console.log(Object.prototype.toString.call(arr)); // "[object Array]"
3.手写instanceof⭐⭐⭐
//遍历A的原型链,如果找到B.prototype,返回true,否则返回false
const _instanceof = (A, B) => {
let p = A
while(p) {
if(p === B.prototype) {
return true
}
p = p.__proto__
}
return false
}
4.var和let const的区别⭐⭐⭐⭐⭐
- var是ES5语法,let const是ES6语法,var有变量提升,但其实let和const也会提升,但是会有暂时性死区,在定义前不可用
- var和let是变量,可修改,const是常量,不可修改,但是如果使用const声明的是对象的话,是可以修改对象里面的值的
- let const有块级作用域,var没有
5.关于数组的遍历方案,有哪些区别?⭐⭐⭐⭐
for:
最基础常见的一种循环,不用多说
- continue 语句用来跳出本次循环,但会继续执行后面的循环。
- break 语句用来结束循环,后面的循环不会再执行。
- return 并不能用来跳出for循环,return语句只能出现在函数体内,它会终止函数的执行,并返回一个指定的值。
forEach:
对数组的每个元素执行一次提供的函数,其中函数有三个参数,依次为:当前循环项的内容、当前循环的索引、循环的数组
map():
方法会依次循环每一项,并且返回结果映射组成一个新的数组
使用forEach、map不能中断循环,方法会将每项内容都执行完成才会结束循环。
for in:
遍历的是key(可遍历对象、数组或字符串的key),最好用来遍历对象
使用for-in可以遍历数组,但是会存在以下问题:
- index索引为字符串型数字(注意,非数字),不能直接进行几何运算。
- 遍历顺序有可能不是按照实际数组的内部顺序(可能按照随机顺序)。
- 使用for-in会遍历数组所有的可枚举属性,包括原型。通常需要配合hasOwnProperty()方法判断某个属性是否该对象的实例属性,来将原型对象从循环中剔除。
for-of:遍历的是value(可遍历对象、数组或字符串的value) - 这是最简洁、最直接的遍历数组元素的语法,这个方法避开了for-in循环的所有缺陷。
- 与forEach()不同的是,它可以正确响应break、continue和return语句。
- 因此建议是使用for-of遍历数组,因为for-of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name。
6.什么是原型链?⭐⭐⭐⭐⭐
原型关系:
- 每个构造函数都有显示原型prototype
- 每个实例都有隐式原型_proto_
- 实例的_proto_指向对应构造函数的prototype
- 获取属性和方法时,先从自身属性和方法寻找,如果找不到则自动去_proto_中寻找
原型链:
当访问一个对象的某个属性时,会现在这个对象本身属性上查找,如果没有找到,则会去他的__proro__隐式原型上查找,即他的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,一直往上层查找,直到到null还没有找到,则返回undefined,Object.prototype.—proto—===null这,这样一层一层向上查找就会形成一个链式结构,我们称为原型链
7.什么是自由变量⭐⭐⭐⭐
自由变量:
一个变量在当前作用域没有定义,但被使用了。会向上级作用域一层一层依次寻找,直到找到为止,如果到全局作用域都没找到,则报错
所有自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方
8. 什么是作用域,作用域链⭐⭐⭐⭐⭐
作用域:
- 作用域决定了代码区块中变量和其他资源的可见性,作用域就是一个独立的地盘,让变量不会外泄、暴露出去
- 也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
- ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6的到来,为我们提供了块级作用域
作用域链: - 当所需要的变量在所在的作用域中查找不到的时候,它会一层一层向上查找,
- 直到找到全局作用域还没有找到的时候,就会放弃查找。这种一层一层的关系,就是作用域链
作用域和执行上下文:
JavaScript的执行分为:解释和执行两个阶段
解释阶段:- 词法分析
- 语法分析
- 作用域规则确定
执行阶段: - 创建执行上下文
- 执行函数代码
- 垃圾回收
- JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。
- 执行上下文最明显的就是this的指向是执行时确定的,而作用域访问的变量是编写代码的结构确定的。
作用域和执行上下文之间最大的
区别是:
- 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。
- 同一个作用域下,不同的调用会产生不同的执行上下文环境,所以this指向就不同
9. 说一说this⭐⭐⭐⭐⭐
- this取值,是在函数执行的时候确定的,不是在定义的时候确定的,this默认情况下指向window,严格模式下为undefined
- 隐式绑定:
即this指向距离其最近的调用者,所谓的最近的调用者就函数前面最近的一个对象。
显示绑定:
1、call, apply, bind 中的this会指向传入的第一个参数
2、如果这些函数调用时没有传入参数,则指向默认对象(window或undefined) - 构造函数中的this指向该函数创建的实例对象
- 箭头函数本身并不存在this,其this是由其父级作用域继承而来,箭头函数中的this无法通过bind、call、apply进行修改。
- 立即执行函数中的this就一句话:永远指向全局window
10. 谈一谈闭包⭐⭐⭐⭐⭐
闭包:
含义:闭包就是能够读取其他函数内部变量的函数
理解:
- 无论通过哪种方式将内部的函数传递到所在的词法作用域以外,它都会持有对原始作用域的引用,无论在何处执行这个函数都会使用闭包。
- 让这些变量的值始终保存在内存中,这是因为闭包的执行依赖外部函数中的变量,只有闭包执行完,才会释放变量所占的内存 应用:
- 闭包的应用比较典型是定义模块,我们将操作函数暴露给外部,而细节隐藏在模块内部
- 防抖节流
11. 手写apply⭐⭐⭐
Function.prototype.myApply = function(context) {
const ctx = context || window
//最重要的一步,1.myApply内部的this是指向调用者fn函数的。
//2.ctx.func就是fn函数,ctx调用了fn函数,因此fn函数内部的this指向ctx
ctx.func = this
let result = arguments[1] ? ctx.func([...arguments[1]]) : ctx.func()
delete ctx.func
return result
}
12. 手写bind⭐⭐⭐
Function.prototype.bind1 = function () {
//将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
//获取this(数组第一项)
const t = args.shift();
//fn1.bind(...)中的fn1
const self = this
//返回一个函数
return function () {
return self.apply(t, args)
}
}
13.手写flatern,考虑多层级⭐⭐⭐
function flat(arr) {
const isDeep = arr.some(item => item instanceof Array)
if (!isDeep) {
return arr //已经是flatern
}
const res = [].concat(...arr) //arr里的每个元素都会通过concat连接
return flat(res)
}
14. 手写new⭐⭐⭐
function myNew(fn, ...args) {
// 创建一个空对象
let obj = {}
// 使空对象的隐式原型指向原函数的显式原型
obj.__proto__ = fn.prototype
// this指向obj
let result = fn.apply(obj, args)
// 返回
return result instanceof Object ? result : obj
}
15. 手写trim ⭐⭐⭐
String.prototype.trim = function () {
return this.replace(/^\s+/, '').replace(/\s+$/, '')
}
16. 手写柯里化 ⭐⭐⭐
实现例如add(1)(2)(3)(4)=10 add(1)(1,2,3)(2)=9
指的是将一个接受多个参数的函数,变为接受一个参数返回一个函数的固定形式
function add() {
let args = Array.from(arguments)
let adder = function() {
args.push(...arguments)
return adder
}
adder.toString = function() {
return args.reduce(function(a, b){
return a + b
}, 0)
}
return adder
}
let a = add(1,2,3)
let b = add(1)(2)(3)
console.log(a)
console.log(b)
console.log(a==6)
console.log(b==6)
/*
ƒ () {
args.push(...arguments)
return adder
}
ƒ () {
args.push(...arguments)
return adder
}
true
true
*/
17. 手写深度比较⭐⭐⭐⭐
//判断是否是对象或数组
function isObject(obj) {
return typeof obj === 'object' && obj !== 'null'
}
function isEqual(obj1, obj2) {
if (!isObject(obj1) || !isObject(obj2)) {
//值类型(注意,参与equal的一般不会是函数)
return obj1 === obj2
}
//两个都是对象或数组,而且不相等
//1.先取出obj1和obj2的keys,比较个数
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
if (obj1Keys.length !== obj2Keys.length) {
return false
}
//2.以obj1为基准,和obj2一次递归比较
for (let key in obj1) {
if (!isEqual(obj1[key], obj2[key])) {
return false
}
}
return true
}
18. 手写深拷贝⭐⭐⭐⭐⭐
function deepClone(obj = {}) {
if(typeof obj !== 'object' || obj == null) {
//obj是null,或者不是对象或数组,直接返回
return obj;
}
//初始化返回结果
let result;
if (obj instanceof Array) {
result = [];
} else {
result = {};
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
//递归调用
result[key] = deepClone(obj[key]);
}
}
return result;
}
19. 介绍一下什么是DOM?⭐⭐⭐
DOM:
- 即文档对象模型,DOM定义了访问HTML和XML文档的标准。
- DOM把整个页面映射为一个多层的节点结构,html或xml页面中的每个组成部分都是某种类型的节点,这些节点又包含着不同类型的数据。
- 它允许程序和脚本动态地访问和更新文档的内容、结构和样式。
- DOM节点有元素节点,文本节点,属性节点等12种,有很多方法获取节点和操作节点
20. 介绍一下ajax并手写⭐⭐⭐⭐⭐
ajax原理:
- 通过浏览器的javascript对象XMLHttpRequest(Ajax引擎)对象向服务器发送异步请求并接收服务器的响应数据,然后用javascript来操作DOM而更新页面。这其中最关键的一步就是从服务器获得请求数据。
- 用户的请求间接通过Ajax引擎发出而不是通过浏览器直接发出,同时Ajax引擎也接收服务器返回响应的数据,所以不会导致浏览器上的页面全部刷新。
// 手写一般写promise封装的就可以了
function ajaxPromiseGet() {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', 'test.json', true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText))
} else if (xhr.status === 404) {
reject(new Error('404 not found'))
}
}
}
xhr.send(null)
})
return p
}
xhr.readyState:
- 0-(未初始化)还没有调用send()方法
- 1-(载入)已调用send()方法,正在发送请求
- 2-(载入完成)send()方法执行完成,已经接收到全部相应内容
- 3-(交互)正在解析响应内容
- 4-(完成)相应内容解析完成,可以在客户端调用
xhr.status:
- 2xx-表示成功处理请求,如200
- 3xx-需要重定向。浏览器直接跳转,如301 302 304
- 4xx-客户端请求错误,如404 403
- 5xx-服务端错误
21. 介绍一下什么是JSON?⭐⭐⭐
- json是一种数据格式,本质是一段字符串
- json结构和JS对象结构一致,对JS语言更友好
- window.JSON是一个全局对象:JSON.stringify,JSON.parse
- 不能有单引号,都需要双引号
22.介绍一下JS的垃圾回收机制⭐⭐⭐⭐
垃圾回收机制回收的对象:
- JavaScript 中的原始类型的值存放在栈内存中,由系统自动分配释放。
- 引用类型的值存放于堆内存中,其生命周期由JavaScript引擎的垃圾回收机制决定。
垃圾回收机制分为:引用计数和标记清除
引用计数:
- 基本思路就是对每个值都记录其被引用的次数。当一个值的引用数为0的时候,说明没有任何变量引用它,就可以安全的回收其内存了。
- 垃圾回收器会在下次进行垃圾回收的时候,释放引用次数为0的值的内存。 引用记数的问题:
- 循环引用。即对象A有一个属性指向B,B也有一个属性指向A。
- 这样A、B对象的引用计数都为2,就永远不会被垃圾回收器回收。久而久之,会造成大量的内存无法被回收,造成内存泄漏。由于存在比较明显的问题,所以主要存在于早期的IE浏览器
标记清除:
可达性:
可达值是那些以某种方式可访问或可用的值,他们不会被垃圾回收机制清除、释放,可达性是判断一个值是否会被垃圾回收的重要依据。
下面是一些固有可达值的集合,所占用的空间不能被释放。 - 当前函数的局部变量和参数
- 嵌套调用时调用链上所有函数的变量与参数
- 全局变量以及还有一些内部的
这些值称为根,如果一个值可以从根通过引用或引用链被访问,就被认为是可达的。
标记清除的过程 垃圾收集器会定期执行以下步骤,进行垃圾回收。
- 垃圾收集器找到所有根并标记他们
- 遍历并标记根的引用
- 然后标记引用的引用,直至标记所有可达的对象
- 剩余没有被标记的对象都会被删除 现今主流浏览器都采用标记清除,来管理引用值的内存
24.介绍一下什么是跨域⭐⭐⭐⭐⭐
重点:
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
为什么拦截?
- 同源策略:ajax请求时,浏览器要求当前网页和server必须同源,浏览器安全的基石是同源策略
- 同源:协议,域名,端口,三者必须一致
- 目的:为了保证用户信息的安全,防止恶意的网站窃取数据。
例外:
- 加载图片,css,js可无视同源策略
<img/>可用于统计打点,可使用第三方统计服务<link/>``<script>可使用CDN,CDN一般都是外域
25. 介绍一下jsonp跨域?⭐⭐⭐⭐⭐
jsonp原理:
script标签src属性中的链接可以访问跨域的js脚本,利用这个特性,用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据返回,服务端不再返回JSON格式的数据,而是返回一段调用某个函数的js代码,在src中进行了调用,这样实现了跨域
jsonp优缺点
- JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。
- 缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
26. 介绍一下CORS跨域?⭐⭐⭐⭐⭐
CORS跨域:
CORS是跨源AJAX请求的根本解决方法。JSONP只能发GET请求,但是CORS允许任何类型的请求。
浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
浏览器将CORS请求分成两类:简单请求和非简单请求
简单请求:
需要同时满足下面两大条件:
(1) 请求方法是以下三种方法之一:
- HEAD
- GET
- POST (2)HTTP的头信息 Request Headers 不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
- 对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段
- origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求
- 如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段
- 最重要的是Access-Control-Allow-Origin 该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
- 还有一些字段控制是否发送cookie等
非简单请求: - 非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
- 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为预检请求
- "预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。
- 除了Origin字段,"预检"请求的头信息包括两个特殊字段。
- Access-Control-Request-Method 该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
- Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
- 服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后确认允许跨源请求,就可以做出回应,回应的关键同样是Access-Control-Allow-Origin字段 PS:
- 可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。
- 服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
27.介绍一下Nginx?⭐⭐⭐⭐⭐
- 正向代理:一般用于个人访问某服务内部,比如你要访问Google,但是通常情况下,你无法访问,这时聪明的我们会花钱或者免费找一个代理服务器,让它去访问Google,然后再通过它返回数据给你,此时对于Google来说,他不知道具体实际访问它的client是谁,此过程为正向代理。
- 反向代理:一般用于公司提供对外服务,比如一般的公司,为了保证内网的安全,阻止web攻击,实现负载均衡减少请求压力,会在web节点之前搭建一个代理服务器,比如nginx,此时,用户访问的是nginx代理服务器,而对于用户来说,他不知道具体为他提供服务的是哪个真实的server节点,此过程为反向代理。
举个栗子:
当前前端域名为:http://127.0.0.1
后端server域名为:https://github.com
我想直接获取https://github.com的内容肯定是不行的,会出现跨域
现在就需要需要启动一个nginx服务器,在配置文件nginx.conf中做出如下配置
server {
listen 80;
server_name 127.0.0.1;
location / {
proxy_pass http://127.0.0.1:3000;
}
location ~ /api/ {
proxy_pass http://github.com;
}
}
上面的配置的可以理解为:
监听80端口(Nginx默认启动了80端口),将
http://127.0.0.1的所有请求服务转发到127.0.0.1端口为3000;
将http://127.0.0.1/api/或者http://127.0.0.1/api/xxxxx请求转发到http://github.com服务器和服务器中是不存在跨域的,nginx正是利用了这一点,将前端访问同源服务器,而同源服务器将请求转发到不同域的服务器从而获取数据
写在最后
非常感谢您到阅读到最后,如果您觉得对您有帮助的话,希望能够点个赞,有任何问题都可以联系我,希望能够一起进步。
最后祝您前程似锦,我们各自攀登,高处相见🌈!