专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。解决某一问题的具体算法实现。
- 函数式编程:把运算过程尽量写成一系列嵌套的函数调用。
函数式编程强调没有”副作用”,意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
所谓”副作用”,指的是函数内部与外部交互(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
40、http 常见的响应状态码 ?
100——客户必须继续发出请求
101——客户要求服务器根据请求转换HTTP协议版本
200——交易成功
201——提示知道新文件的URL
202——接受和处理、但处理未完成
203——返回信息不确定或不完整
204——请求收到,但返回信息为空
205——服务器完成了请求,用户代理必须复位当前已经浏览过的文件
206——服务器已经完成了部分用户的GET请求
300——请求的资源可在多处得到
301——删除请求数据
302——在其他地址发现了请求数据
303——建议客户访问其他URL或访问方式
304——客户端已经执行了GET,但文件未变化
305——请求的资源必须从服务器指定的地址得到
306——前一版本HTTP中使用的代码,现行版本中不再使用
307——申明请求的资源临时性删除
400——错误请求,如语法错误
401——请求授权失败
402——保留有效ChargeTo头响应
403——请求不允许
404——没有发现文件、查询或URl
405——用户在Request-Line字段定义的方法不允许
406——根据用户发送的Accept拖,请求资源不可访问
407——类似401,用户必须首先在代理服务器上得到授权
408——客户端没有在用户指定的饿时间内完成请求
409——对当前资源状态,请求不能完成
410——服务器上不再有此资源且无进一步的参考地址
411——服务器拒绝用户定义的Content-Length属性请求
412——一个或多个请求头字段在当前请求中错误
413——请求的资源大于服务器允许的大小
414——请求的资源URL长于服务器允许的长度
415——请求资源不支持请求项目格式
416——请求中包含Range请求头字段,在当前请求资源范围内没有range指示值,请求也不包含If-Range请求头字段
417——服务器不满足请求Expect头字段指定的期望值,如果是代理服务器,可能是下一级服务器不能满足请求
500——服务器产生内部错误
501——服务器不支持请求的函数
502——服务器暂时不可用,有时是为了防止发生系统过载
503——服务器过载或暂停维修
504——关口过载,服务器使用另一个关口或服务来响应用户,等待时间设定值较长
505——服务器不支持或拒绝支请求头中指定的HTTP版本
41、 什么是事件流以及事件流的传播机制 ?
事件触发后,从开始找目标元素,然后执行目标元素的事件,再到离开目标元素的整个过程称之为事件流。
W3C标准浏览器事件流的传播分为3个阶段:捕获阶段、目标阶段、冒泡阶段
- 捕获阶段指找目标元素的过程,这个找的过程,是从最大的document对象到html,再到body,。。。直到目标元素。
- 找到目标元素后,调用执行他绑定事件时对应的处理函数,这个过程被称之为目标阶段。
- 当目标元素的事件执行结束后,再从目标元素,到他的父元素。。。body、html再到document的过程,是冒泡阶段。
42、模块化语法 ? commonJS AMD CMD ES6 Module
- commonJS是nodejs自带的一种模块化语法,将一个文件看做是一个模块,可以将文件中导出的时候,被另一个文件导入使用。导出使用:
module.exports
导出。导入使用:require
函数导入。 - AMD是社区开发的模块化语法,需要依赖
require.js
实现,分为定义模块,导出数据和导入模块,使用数据。AMD语法的导入是依赖前置的,也就是说,需要用到的文件需要在第一次打开页面全部加载完成,造成的后果就是首屏加载很慢,后续操作会很流畅。 - CMD是玉伯开发的模块化语法,需要依赖
sea.js
实现,也分为模块定义导出,和模块导入使用数据。CMD语法可以依赖前置,也可以按需导入,缓解了AMD语法的依赖前置。 - ES6的模块化语法,类似于commonJS的语法,分为数据导出和数据导入,导入导出更加灵活。
43、 什么是懒加载和预加载 ?
-
懒加载:懒加载也叫延迟加载,延迟加载网络资源或符合某些条件时才加载资源。常见的就是图片延时加载。懒加载的意义:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。懒惰实现方式:
- 1.第一种是纯粹的延迟加载,使用setTimeOut或setInterval进行加载延迟.
- 2.第二种是条件加载,符合某些条件,或触发了某些事件才开始异步下载。
- 3.第三种是可视区加载,即仅加载用户可以看到的区域,这个主要由监控滚动条来实现,一般会在距用户看到某图片前一定距离便开始加载,这样能保证用户拉下时正好能看到图片。
-
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。预加载应用如广告弹窗等。
44、token 一般存放在哪里 ? 为什么不存放在 cookie 内 ?
token一般放在本地存储中。token的存在本身只关心请求的安全性,而不关心token本身的安全,因为token是服务器端生成的,可以理解为一种加密技术。但如果存在cookie内的话,浏览器的请求默认会自动在请求头中携带cookie,所以容易受到csrf攻击。
45、 less 和 sass 的区别 ?
- 编译环境不一样,sass是服务器端处理的,可以用Ruby、node-sass来编译;less需要引入less.js来处理输出,也可以使用工具在服务器端处理成css,也有在线编译的。
- 变量定义符不一样,less用的是@,而sass用$。
- sass支持分支语句,less不支持
44、浏览器的同源策略机制 ?
同源策略,又称SOP,全称Same Origin Policy,是浏览器最基本的安全功能。站在浏览器的较短看网页,如果网络上的接口可以不受限制、无需授权随意被人调用,那将是一个非常严重的混乱场景。浏览器为了安全有序,内部实现了同源策略。
同源策略,指的是浏览器限制当前网页只能访问同源的接口资源。
所谓同源,指当前页面和请求的接口,两方必须是同协议、且同域名、且同端口。只要有一个不相同,则会受到浏览器约束,不允许请求。
但当一个项目变的很大的时候,将所有内容放在一个网站或一个服务器中会让网站变的臃肿且性能低下,所以,在一些场景中,我们需要跨过同源策略,请求到不同源的接口资源,这种场景叫跨域。
跨域大致有3种方案:
- jsonp
这种方式是利用浏览器不限制某些标签发送跨域请求,例如link、img、iframe、script。通常请求请求回来的资源要在js中进行处理,所以jsonp跨域是利用script标签进行发送,且这种请求方式只能是get请求。
- cors
这种方式是让接口资源方面进行授权,授权允许访问。在接口资源处添加响应头即可通过浏览器的同源策略,响应头具体的键值对如下:
{Access-Control-Allow-Origin: '*'}
- proxy
这种方式属于找外援的一种方式,浏览器只能限制当前正在打开的web页面发送请求,但无法限制服务器端请求接口资源。所以我们可以将请求发送到自己服务器,然后自己服务器去请求目标接口资源,最后自己服务器将接口资源返回给当前页面,类似于找外援代替自己请求目标接口资源。
这种方式通常要对服务器进行代理配置,需要对apache服务器、nginx服务器、nodejs服务器进行配置。
45、 浏览器的缓存有哪些 ? 什么时候使用强制缓存 ? 什么时候使用协商缓存 ?
当我们访问同一个页面时,请求资源、数据都是需要一定的耗时,如果可以将一些资源缓存下来,那么从第二次访问开始,就可以减少加载时间,提高用户体验,也能减轻服务器的压力。
浏览器缓存分为强缓存和协商缓存,当存在缓存时,客户端第一次向服务器请求数据时,客户端会缓存到内存或者硬盘当中,当第二次获取相同的资源,强缓存和协商缓存的应对方式有所不同。
强缓存:当客户端第二次向服务器请求相同的资源时,不会向服务器发送请求,而是直接从内存/硬盘中间读取。缓存由服务器的响应头里 cache-control 和 expires 两个字段决定
协商缓存:当客户端第二次向服务器请求相同的资源时,先向服务器发送请求"询问"该请求的文件缓存在ben'd与服务器相比是否更改,如果更改,则更新文件,如果没有就从内存/硬盘中读取。协商缓存由 last-modified 和 etag两个字段决定
46、 数组方法 forEach 和 map 的区别 ?
forEach和map都是循环遍历数组中的每一项。forEach() 和 map() 里面每一次执行匿名函数都支持3个参数:数组中的当前项item,当前项的索引index,原始数组input。匿名函数中的this都是指Window。只能遍历数组。
他们的区别是:forEach没有返回值,但map中要有返回值,返回处理后的所有新元素组成的数组。
47、 什么是函数作用域 ? 什么是作用域链 ?
作用域就是在代码执行过程中,形成一个独立的空间,让空间内的变量不会泄露在空间外,也让独立空间内的变量函数在独立空间内运行,而不会影响到外部的环境。
作用域分为全局作用域和局部作用域,也就是本来有一个巨大的空间,空间内定义的函数内部,就形成了一个独立的小空间,全局作用域是最大的作用域。
但是当独立空间内的数据不能满足需求时,是可以从外部获取数据的,也就是说这样的独立空间之间是可以有层级关系的,外部的空间不可以从内部的空间获取数据,但内部的空间可以。当子级空间在父级空间中获取数据的时,父级空间没有的话,父级空间也会到他的父级空间中查找数据,这样形成的链式结构叫作用域链。
当将一个变量当做值使用时,会先在当前作用域中查找这个变量的定义和数据,如果没有定义的话,就会去父级作用域中查找,如果父级作用域中有的话就使用这个值,如果父级作用域中也没有的话,就通过父级作用域查找他的父级作用域,直到找到最大的作用域-全局,如果全局也没有就报错。
当将一个变量当做数据容器存储,也就是给变量赋值的时候,也要先在自己作用域中查找变量的定义,如果没有就在上一级作用域中查找,直到全局,如果全局作用域中也没有这个变量的定义,就在全局定义这个变量并赋值。
48、 ES6 中 Set 和 Map 的原理 ?
Set 是无重复值的有序列表。根据 `Object.is()`方法来判断其中的值不相等,以保证无重复。Set 会自动移除重复的值,因此你可以使用它来过滤数组中的重复值并返回结果。Set并不是数组的子类型,所以你无法随机访问其中的值。但你可以使用`has()` 方法来判断某个值是否存在于 Set 中,或通过 `size` 属性来查看其中有多少个值。Set 类型还拥有`forEach()`方法,用于处理每个值
Map 是有序的键值对,其中的键允许是任何类型。与 Set 相似,通过调用 `Object.is()`方法来判断重复的键,这意味着能将数值 5 与字符串 "5" 作为两个相对独立的键。使用`set()` 方法能将任何类型的值关联到某个键上,并且该值此后能用 `get()` 方法提取出来。Map 也拥有一个 `size` 属性与一个 `forEach()` 方法,让项目访问更容易。
49、 0.1 + 0.2 为什么不等于 0.3, 在项目中遇到要怎么处理 ?
计算机内部存储数据使用2进制存储,两个数字进行的数学运算,首先是将这两个数字以2进制形式,存储在计算机内部,然后在计算机内部使用两个2进制数字进行计算,最后将计算结果的2进制数字转为10进制展示出来。
由于10进制的小数在转2进制的时候,规则是小数部分乘以2,判断是否得到一个整数,如果得到整数,转换完成;如果没有得到整数,则继续乘以2判断。所以,0.1和0.2在转换2进制的时候,其实是一个无限死循环,也就是一直乘以2没有得到整数的时候,但计算机内部对于无限死循环的数据,会根据一个标准保留52位。也就是说,计算机内部在存储0.1和0.2的时候,本来就不精准,两个不精准的小数在计算后,距离精准的结果是有一定误差的。
项目中碰到这种情况,有3种处理方法:
- 将小数乘以10的倍数,转为整数,然后计算,计算完成后,再缩小10的倍数,例如:
var result = ((0.1 * 10) + (0.2 * 10)) / 10 // result === 0.3
- 使用数字的toFixed方法,强制保留小数点后多少位,例
var result = (0.1 + 0.2).toFixed(2) // result === 0.30
- 自定义数字运算方法,当需要进行数学运算的时候,不直接进行,调用自定义的方法进行,例:(加法封装)
function add(...args){ var num = args.find(item => { if(item != 0 && !item){ throw new Error("数学运算要使用数字") } }) var arr = args.map(item => { var index = (item+'').indexOf('.') if(index >= 0){ return (item+'').split('.')[1].length } }) arr = arr.filter(item => item) if(arr.length){ var max = Math.max(...arr) var data = args.map(item => item * Math.pow(10, max)) var data.reduce((a, b) => a + b) / Math.pow(10, max) }else{ var data = args return data.reduce((a, b) => a + b) } } // 调用使用: var num1 = add(0.1, 0.2) console.log(num1); // 0.3 var num2 = add(1, 2) console.log(num2); // 3 var num3 = add(1, 2.1) console.log(num3); // 3.1
50、 什么是模块化思想 ?
就是JS中将不同功能的代码封装在不同的文件中, 互相引用时不会发生命名冲突的一种思想, 大多数情况下, 一个文件就是一个模块
模块化的实现,有多种方案:
- CommonJS:
CommonJS
是nodejs
中使用的模块化规范在 nodejs
应用中每个文件就是一个模块,拥有自己的作用域,文件中的变量、函数都是私有的,与其他文件相隔离。模块导出:module.exports=数据
,模块导入:require('模块文件路径')
- ES6的模块化:
模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export
关键字输出该变量。下面是一个 JS 文件,里面使用export
命令输出变量。
- AMD (Asynchronous Module Definition):
特点: 提倡依赖前置,在定义模块的时候就要声明其依赖的模块:导入模块require([module],callback);
定义模块:define('模块名称', 函数)
。
- CMD (Common Module Definition):
CMD规范是国内SeaJS的推广过程中产生的。提倡就近依赖(按需加载),在用到某个模块的时候再去require。定义模块:define(function (require, exports, module) {})
,使用模块:seajs.use()
51、 说说怎么用js 写无缝轮播图
将所有需要轮播的内容动态复制一份,放在原本的容器中,加定时器让整个容器中的内容滚动轮播,当内容轮播到left值为-原本的内容宽度时,快速将内容切换到left值为0的状态。
52、 JS 如何实现多线程 ?
我们都知道JS是一种单线程语言,即使是一些异步的事件也是在JS的主线程上运行的(具体是怎么运行的,可以看我另一篇博客JS代码运行机制)。像setTimeout、ajax的异步请求,或者是dom元素的一些事件,都是在JS主线程执行的,这些操作并没有在浏览器中开辟新的线程去执行,而是当这些异步操作被操作时或者是被触发时才进入事件队列,然后在JS主线程中开始运行。
首先说一下浏览器的线程,浏览器中主要的线程包括,UI渲染线程,JS主线程,GUI事件触发线程,http请求线程。
JS作为脚本语言,它的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。(这里这些问题我们不做研究)
但是单线程的语言,有一个很致命的确定。如果说一个脚本语言在执行时,其中某一块的功能在执行时耗费了大量的时间,那么就会造成阻塞。这样的项目,用户体验是非常差的,所以这种现象在项目的开发过程中是不允许存在的。
其实JS为我们提供了一个Worker的类,它的作用就是为了解决这种阻塞的现象。当我们使用这个类的时候,它就会向浏览器申请一个新的线程。这个线程就用来单独执行一个js文件。
var worker = new Worker(js文件路径);
那么这个语句就会申请一个线程用来执行这个js文件。这样也就实现了js的多线程。
53、 闭包的使用场景 ?
一个函数被当作值返回时,也就相当于返回了一个通道,这个通道可以访问这个函数词法作用域中的变量,即函数所需要的数据结构保存了下来,数据结构中的值在外层函数执行时创建,外层函数执行完毕时理因销毁,但由于内部函数作为值返回出去,这些值得以保存下来。而且无法直接访问,必须通过返回的函数。这也就是私有性。
本来执行过程和词法作用域是封闭的,这种返回的函数就好比是一个虫洞,开了挂。
闭包的形成很简单,在执行过程完毕后,返回函数,或者将函数得以保留下来,即形成闭包。
- 防抖:
function debounce(fn, interval) { let timer = null; // 定时器 return function() { // 清除上一次的定时器 clearTimeout(timer); // 拿到当前的函数作用域 let _this = this; // 拿到当前函数的参数数组 let args = Array.prototype.slice.call(arguments, 0); // 开启倒计时定时器 timer = setTimeout(function() { // 通过apply传递当前函数this,以及参数 fn.apply(_this, args); // 默认300ms执行 }, interval || 300) } }
- 节流:
function throttle(fn, interval) { let timer = null; // 定时器 let firstTime = true; // 判断是否是第一次执行 // 利用闭包 return function() { // 拿到函数的参数数组 let args = Array.prototype.slice.call(arguments, 0); // 拿到当前的函数作用域 let _this = this; // 如果是第一次执行的话,需要立即执行该函数 if(firstTime) { // 通过apply,绑定当前函数的作用域以及传递参数 fn.apply(_this, args); // 修改标识为null,释放内存 firstTime = null; } // 如果当前有正在等待执行的函数则直接返回 if(timer) return; // 开启一个倒计时定时器 timer = setTimeout(function() { // 通过apply,绑定当前函数的作用域以及传递参数 fn.apply(_this, args); // 清除之前的定时器 timer = null; // 默认300ms执行一次 }, interval || 300) } }
- 迭代器:
var arr =['aa','bb','cc']; function incre(arr){ var i=0; return function(){ //这个函数每次被执行都返回数组arr中 i下标对应的元素 return arr[i++] || '数组值已经遍历完'; } } var next = incre(arr); console.log(next());//aa console.log(next());//bb console.log(next());//cc console.log(next());//数组值已经遍历完
- 缓存:
var fn=(function(){ var cache={};//缓存对象 var calc=function(arr){//计算函数 var sum=0; //求和 for(var i=0;i<arr.length;i++){ sum+=arr[i]; } return sum; } return function(){ var args = Array.prototype.slice.call(arguments,0);//arguments转换成数组 var key=args.join(",");//将args用逗号连接成字符串 var result , tSum = cache[key]; if(tSum){//如果缓存有 console.log('从缓存中取:',cache)//打印方便查看 result = tSum; }else{ //重新计算,并存入缓存同时赋值给result result = cache[key]=calc(args); console.log('存入缓存:',cache)//打印方便查看 } return result; } })(); fn(1,2,3,4,5); fn(1,2,3,4,5); fn(1,2,3,4,5,6); fn(1,2,3,4,5,8); fn(1,2,3,4,5,6);
- getter和setter:
function fn(){ var name='hello' setName=function(n){ name = n; } getName=function(){ return name; } //将setName,getName作为对象的属性返回 return { setName:setName, getName:getName } } var fn1 = fn();//返回对象,属性setName和getName是两个函数 console.log(fn1.getName());//getter fn1.setName('world');//setter修改闭包里面的name console.log(fn1.getName());//getter
- 柯里化:
function curryingCheck(reg) { return function(txt) { return reg.test(txt) } } var hasNumber = curryingCheck(/\d+/g) var hasLetter = curryingCheck(/[a-z]+/g) hasNumber('test1') // true hasNumber('testtest') // false hasLetter('21212') // false
- 循环中绑定事件或执行异步代码
var p1 = "ss"; var p2 = "jj"; function testSetTime(para1,para2){ return (function(){ console.log(para1 + "-" + para2); }) } var test = testSetTime(p1, p2); setTimeout(test, 1000); setTimeout(function(){ console.log(p1 + "-" + p2) },1000)
- 单例模式:
var Singleton = (function () { var instance; function createInstance() { return new Object("I am the instance"); } return { getInstance: function () { if (!instance) { instance = createInstance(); } return instance; } }; })();
54、 常见的兼容问题有哪些 ?
- 获取标签节点:
document.getElementsByClassName(‘类名’)在低版本ie
中不兼容。解决方法是使用其他方式获取:
document.getElementById('id名') document.getElementsByTagName('标签名') document.getElementsByName('name属性值') document.querySelector('css选择器') document.querySelectorAll('css选择器')
获取卷去的高度
// 当有文档声明的时候 document.documentElement.scrollTop document.documentElement.srollLeft // 没有文档声明的时候 document.body.scrollTop document.body.scrollLeft
解决办法使用兼容写法:
// 获取 var t = document.documentElement.scrollTop || document.body.scrollTop var l = document.documentElement.srollLeft || document.body.scrollLeft // 设置 document.documentElement.scrollTop = document.body.scrollTop = 数值 document.documentElement.srollLeft = document.body.scrollLeft = 数值
- 获取样式
// W3C标准浏览器 window.getComputedStyle(元素) // 低版本IE中 元素.currentStyle
- 使用函数封装的方式兼容:
function getStyle(ele,attr){ if(window.getComputedStyle){ return getComputedStyle(ele)[attr] }else{ return ele.currentStyle[attr] } }
- 事件侦听器
// W3C浏览器 ele.addEventListener(事件类型,函数) // 低版本Ie ele.attachEvent('on事件类型',函数)
- 使用函数封装的方式解决:
function bindEvent(ele,type,handler){ if(ele.addEventListener){ ele.addEventListener(type,handler) }else if(ele.attachEvent){ ele.attachEvent('on'+type,handler) }else{ ele['on'+type] = handler } }
- 事件解绑
// W3C浏览器 ele.removeEventListener(事件类型,函数) // 低版本Ie ele.detachEvent('on事件类型',函数)
- 使用函数封装的方式解决:
function unBind(ele,type,handler){ if(ele.removeEventListener){ ele.removeEventListener(type,handler) }else if(ele.detachEvent){ ele.detachEvent('on'+type,handler) }else{ ele['on'+type] = null } }
- 事件对象的获取
// W3C浏览器 元素.on事件类型 = function(e){} 元素.addEventListener(事件类型,fn) function fn(e){ } // 在低版本IE中 元素.on事件类型 = function(){ window.event } 元素.addEventListener(事件类型,fn) function fn(){ window.event }
- 使用短路运算符解决:
元素.on事件类型 = function(e){ var e = e || window.event } 元素.addEventListener(事件类型,fn) function fn(e){ var e = e || window.event }
- 阻止默认行为
// W3C浏览器 元素.on事件类型 = function(e){ e.preventDefault() } // 在低版本IE中 元素.on事件类型 = function(){ window.event.returnValue = false }
- 通过封装函数解决
元素.on事件类型 = function(e){ var e = e || window.event e.preventDefault?e.preventDefault():e.returnValue=false }
- 阻止事件冒泡
// W3C浏览器 元素.on事件类型 = function(e){ e.stopPropagation() } // 在低版本IE中 元素.on事件类型 = function(){ window.event.cancelBubble = true }
- 通过函数封装解决:
元素.on事件类型 = function(e){ var e = e || window.event e.stopPropagation?e.stopPropagation():e.cancelBubble=true }
- 获取精准的目标元素
// W3C浏览器 元素.on事件类型 = function(e){ e.target } // 在低版本IE中 元素.on事件类型 = function(){ window.event.srcElement }
- 通过短路运算符解决:
元素.on事件类型 = function(e){ var e = e || window.event var target = e.target || e.srcElement; }
- 获取键盘码
// W3C浏览器 元素.on事件类型 = function(e){ e.keyCode } // 在低版本火狐中 元素.on事件类型 = function(e){ e.which }
- 通过短路运算符解决:
元素.on事件类型 = function(e){ var e = e || window.event var keycode = e.keyCode || e.which; }
55、 在 JS 中如何阻止事件冒泡 ?
使用事件对象阻止事件冒泡,以前的w3c浏览器中,使用事件对象的方法阻止:
事件对象.stopPropagation()
在ie低版本浏览器中,使用事件对象的属性阻止:
事件对象.cancelBubble = true
现在的w3c浏览器也支持ie低版本浏览器中的写法,所以以前在阻止事件冒泡的时候,需要考虑兼容写法,现在就不需要了,直接用ie低版本浏览器中的写法即可。
56、两个数组 var A = [1, 5, 6]; var B = [2, 6, 7],实现一个方法,找出仅存在于A 或者 仅 存在于B中的所有数字。
学习路线:
这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成黑客大神,这个方向越往后,需要学习和掌握的东西就会越来越多以下是网络渗透需要学习的内容:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!