-
保持连接状态。与HTTP不同的是,
Websocket
需要先建立连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。 -
更好的二进制支持。
Websocket
定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。 -
支持扩展。
Websocket
定义了扩展,用户可以扩展协议、实现部分自定义的子协议。 -
更好的压缩效果。相对于HTTP压缩,
Websocket
在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著提高压缩率。
三、Java API for WebSocket(JSR356)
JSR356
在Java EE7
时归为Java EE
标准的一部分(后来Java EE
更名为Jakarta EE
,世上再无Java EE
,以下统一称Jakarta EE
),所有兼容Jakarta EE
的应用服务器,都必须遵循JSR356
标准的WebSocket
协议API。
根据JSR356
规定, 建立WebSocket
连接的服务器端和客户端,两端对称,可以互相通信,差异性较小,抽象成API,就是一个个Endpoint
(端点),只不过服务器端的叫ServerEndpoint
,客户端的叫ClientEndpoint
。客户端向服务端发送WebSocket
握手请求,建立连接后就创建一个ServerEndpoint
对象。(这里的Endpoint
和Tomcat
连接器里的AbstractEndpoint
名称上有点像,但是两个毫不相干的东西,就像周杰伦和周杰的关系。)
ServerEndpoint
和ClientEndpoint
在API上差异也很小,有相同的生命周期事件(OnOpen
、OnClose
、OnError
、OnMessage
),不同之处是ServerEndpoint
作为服务器端点,可以指定一个URI路径供客户端连接,ClientEndpoint
没有。
1、服务端API
服务器端的Endpoint
有两种实现方式,一种是注解方式@ServerEndpoint
,一种是继承抽象类Endpoint
。
(1)注解方式@ServerEndpoint
首先看看@ServerEndpoint
有哪些要素:
-
value
,可以指定一个URI路径标识一个Endpoint
。 -
subprotocols
,用户在WebSocket
协议下自定义扩展一些子协议。 -
decoders
,用户可以自定义一些消息解码器,比如通信的消息是一个对象,接收到消息可以自动解码封装成消息对象。 -
encoders
,有解码器就有编码器,定义解码器和编码器的好处是可以规范使用层消息的传输。 -
configurator
,ServerEndpoint
配置类,主要提供ServerEndpoint
对象的创建方式扩展(如果使用Tomcat
的WebSocket
实现,默认是反射创建ServerEndpoint
对象)。
@ServerEndpoint
可以注解到任何类上,但是想实现服务端的完整功能,还需要配合几个生命周期的注解使用,这些生命周期注解只能注解在方法上:
-
@OnOpen
建立连接时触发。 -
@OnClose
关闭连接时触发。 -
@OnError
发生异常时触发。 -
@OnMessage
接收到消息时触发。
(2)继承抽象类Endpoint
继承抽象类Endpoint
,重写几个生命周期方法。
怎么没有onMessage
方法,实现onMessage
还需要继承实现一个接口jakarta.websocket.MessageHandler
,MessageHandler
接口又分为Partial
和Whole
,实现的MessageHandler
需要在onOpen
触发时注册到jakarta.websocket.Session
中。
继承抽象类Endpoint
的方式相对于注解方式要麻烦的多,除了继承Endpoint
和实现接口MessageHandler
外,还必须实现一个jakarta.websocket.server.ServerApplicationConfig
来管理Endpoint
,比如给Endpoint
分配URI路径。
而encoders
、decoders
、configurator
等配置信息由jakarta.websocket.server.ServerEndpointConfig
管理,默认实现jakarta.websocket.server.DefaultServerEndpointConfig
。
所以如果使用 Java 版WebSocket
服务器端实现首推注解方式。
2、客户端API
对于客户端API,也是有注解方式和继承抽象类Endpoint
方式。
-
注解方式,只需要将
@ServerEndpoint
换成@ClientEndpoint
。 -
继承抽象类
Endpoint
方式,需要一个jakarta.websocket.ClientEndpointConfig
来管理encoders
、decoders
、configurator
等配置信息,默认实现jakarta.websocket.DefaultClientEndpointConfig
。
3、上下文Session
WebSocket
是一个有状态的连接,建立连接后的通信都是通过jakarta.websocket.Session
保持状态,一个连接一个Session
,每一个Session
有一个唯一标识Id。
Session的主要职责涉及:
-
基础信息管理(
request
信息(getRequestURI
、getRequestParameterMap
、getPathParameters
等)、协议版本getProtocolVersion
、子协议getNegotiatedSubprotocol
等)。 -
连接管理(状态判断
isOpen
、接收消息的MessageHandler
、发送消息的异步远程端点RemoteEndpoint.Async
和同步远程端点RemoteEndpoint.Basic
等)。
4、HandshakeRequest 和 HandshakeResponse
HandshakeRequest
和 HandshakeResponse
了解即可,这两个接口主要用于WebScoket
握手升级过程中握手请求响应的封装,如果只是单纯使用WebSocket
,不会接触到这两个接口。
(1)HandshakeRequest
(2)HandshakeResponse
Sec-WebSocket-Accept
根据客户端传的Sec-WebSocket-Key
生成,如下是Tomcat10.0.6
WebSocket
源码实现中生成Sec-WebSocket-Accept
的算法:
private static String getWebSocketAccept(String key) {
byte[] digest = ConcurrentMessageDigest.digestSHA1(
key.getBytes(StandardCharsets.ISO_8859_1), WS_ACCEPT);
return Base64.encodeBase64String(digest);
}
5、WebSocketContainer
jakarta.websocket.WebSocketContainer
顾名思义,就是WebSocket
的容器,集大成者。其主要职责包括但不限于connectToServer
,客户端连接服务器端,基于浏览器的WebSocket
客户端连接服务器端,由浏览器支持,但是基于Java版的WebSocket
客户端就可以通过WebSocketContainer#connectToServer
向服务端发起连接请求。
(如下使用的是javax.websocket
包,未使用最新的jakarta.websocket
,主要是测试项目基于SpringBoot
+Tomcat9.x
的,Java API for WebSocket版本需要保持一致。)
1、服务器端实现
(1)@ServerEndpoint注解方式
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@ServerEndpoint(value = “/ws/test/{userId}”, encoders = {MessageEncoder.class}, decoders = {MessageDecoder.class}, configurator = MyServerConfigurator.class)
public class WebSocketServerEndpoint {
private Session session;
private String userId;
@OnOpen
public void OnOpen(Session session, @PathParam(value = “userId”) String userId) {
this.session = session;
this.userId = userId;
// 建立连接后,将连接存到一个map里
endpointMap.put(userId, this);
Message message = new Message(0, "connected, hello " + userId);
sendMsg(message);
}
@OnClose
public void OnClose() {
// 关闭连接时触发,从map中删除连接
endpointMap.remove(userId);
System.out.println(“server closed…”);
}
@OnMessage
public void onMessage(Message message) {
System.out.println(“server recive message=” + message.toString());
}
@OnError
public void onError(Throwable t) throws Throwable {
this.session.close(new CloseReason(CloseReason.CloseCodes.CLOSED_ABNORMALLY, “系统异常”));
t.printStackTrace();
}
/**
-
群发
-
@param data
*/
public void sendAllMsg(Message data) {
for (WebSocketServerEndpoint value : endpointMap.values()) {
value.sendMsgAsync(data);
}
}
/**
-
推送消息给指定 userId
-
@param data
-
@param userId
*/
public void sendMsg(Message data, String userId) {
WebSocketServerEndpoint endpoint = endpointMap.get(userId);
if (endpoint == null) {
System.out.println("not conected to " + userId);
return;
}
endpoint.sendMsgAsync(data);
}
private void sendMsg(Message data) {
try {
this.session.getBasicRemote().sendObject(data);
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (EncodeException e) {
e.printStackTrace();
}
}
private void sendMsgAsync(Message data) {
this.session.getAsyncRemote().sendObject(data);
}
// 存储建立连接的Endpoint
private static ConcurrentHashMap<String, WebSocketServerEndpoint> endpointMap = new ConcurrentHashMap<String, WebSocketServerEndpoint>();
}
每一个客户端与服务器端建立连接后,都会生成一个WebSocketServerEndpoint
,可以通过一个Map将其与userId
对应存起来,为后续群发广播和单独推送消息给某个客户端提供便利。
注意:@ServerEndpoint
的encoders
、decoders
、configurator
等配置信息在实际使用中可以不定义,如果项目简单,完全可以用默认的。
如果通信消息被封装成一个对象,如示例的Message
(因为源码过于简单就不展示了,属性主要有code
、msg
、data
),就必须提供编码器和解码器。也可以在每次发送消息时硬编码转为字符串,在接收到消息时转为Message
。有了编码器和解码器,显得比较规范,转为字符串由编码器做,字符串转为对象由解码器做,但也使得架构变复杂了,视项目需求而定。
Configurator
的用处就是自定义Endpoint
对象创建方式,默认Tomcat提供的是通过反射。WebScoket
是每个连接都会创建一个Endpoint对象,如果连接比较多,很频繁,通过反射创建,用后即毁,可能不是一个好主意,所以可以搞一个对象池,用过回收,用时先从对象池中拿,有就重置,省去实例化分配内存等消耗过程。
如果使用SpringBoot
内置Tomcat
、undertow
、Netty
等,接入WebSocket
时除了加@ServerEndpoint
还需要加一个@Component
,再给Spring注册一个ServerEndpointExporter
类,这样,服务端Endpoint就交由Spring
去扫描注册了。
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
ServerEndpointExporter serverEndpointExporter = new ServerEndpointExporter();
return serverEndpointExporter;
}
}
外置Tomcat就不需要这么麻烦,Tomcat会默认扫描classpath
下带有@ServerEndpoint
注解的类。(SpringBoot
接入Websocket
后续会单独出文章讲解,也挺有意思的)
(2)继承抽象类Endpoint方式
import javax.websocket.*;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
public class WebSocketServerEndpoint extends Endpoint {
private Session session;
private String userId;
@Override
public void onOpen(Session session, EndpointConfig endpointConfig) {
this.session = session;
this.userId = session.getPathParameters().get(“userId”);
session.addMessageHandler(new MessageHandler());
endpointMap.put(userId, this);
Message message = new Message(0, "connected, hello " + userId);
sendMsg(message);
}
@Override
public void onClose(Session session, CloseReason closeReason) {
endpointMap.remove(userId);
}
@Override
public void onError(Session session, Throwable throwable) {
throwable.printStackTrace();
}
/**
-
群发
-
@param data
*/
public void sendAllMsg(Message data) {
for (WebSocketServerEndpoint value : endpointMap.values()) {
value.sendMsgAsync(data);
}
}
/**
-
推送消息给指定 userId
-
@param data
-
@param userId
*/
public void sendMsg(Message data, String userId) {
WebSocketServerEndpoint endpoint = endpointMap.get(userId);
if (endpoint == null) {
System.out.println("not conected to " + userId);
return;
}
endpoint.sendMsgAsync(data);
}
private void sendMsg(Message data) {
try {
this.session.getBasicRemote().sendObject(data);
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (EncodeException e) {
e.printStackTrace();
}
}
private void sendMsgAsync(Message data) {
this.session.getAsyncRemote().sendObject(data);
}
private class MessageHandler implements javax.websocket.MessageHandler.Whole {
@Override
public void onMessage(Message message) {
System.out.println(“server recive message=” + message.toString());
}
}
private static ConcurrentHashMap<String, WebSocketServerEndpoint> endpointMap = new ConcurrentHashMap<String, WebSocketServerEndpoint>();
}
继承抽象类Endpoint
方式比加注解@ServerEndpoint
方式麻烦的很,主要是需要自己实现MessageHandler
和ServerApplicationConfig
。@ServerEndpoint
的话都是使用默认的,原理上差不多,只是注解更自动化,更简洁。
MessageHandler
做的事情,一个@OnMessage
就搞定了,ServerApplicationConfig
做的URI映射、decoders
、encoders
,configurator
等,一个@ServerEndpoint
就可以了。
import javax.websocket.Decoder;
import javax.websocket.Encoder;
import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class MyServerApplicationConfig implements ServerApplicationConfig {
@Override
public Set getEndpointConfigs(Set<Class<? extends Endpoint>> set) {
Set result = new HashSet();
List<Class<? extends Decoder>> decoderList = new ArrayList<Class<? extends Decoder>>();
decoderList.add(MessageDecoder.class);
List<Class<? extends Encoder>> encoderList = new ArrayList<Class<? extends Encoder>>();
encoderList.add(MessageEncoder.class);
if (set.contains(WebSocketServerEndpoint3.class)) {
ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder
.create(WebSocketServerEndpoint3.class, “/ws/test3”)
.decoders(decoderList)
.encoders(encoderList)
.configurator(new MyServerConfigurator())
.build();
result.add(serverEndpointConfig);
}
return result;
}
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set
return set;
}
}
如果使用SpringBoot
内置Tomcat
,则不需要ServerApplicationConfig
了,但是需要给Spring注册一个ServerEndpointConfig
。
@Bean
public ServerEndpointConfig serverEndpointConfig() {
List<Class<? extends Decoder>> decoderList = new ArrayList<Class<? extends Decoder>>();
decoderList.add(MessageDecoder.class);
List<Class<? extends Encoder>> encoderList = new ArrayList<Class<? extends Encoder>>();
encoderList.add(MessageEncoder.class);
ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder
.create(WebSocketServerEndpoint3.class, “/ws/test3/{userId}”)
.decoders(decoderList)
.encoders(encoderList)
.configurator(new MyServerConfigurator())
.build();
return serverEndpointConfig;
}
(3)早期Tomcat7中Server端实现对比
Tomcat7早期版本7.0.47之前还没有出JSR 356
时,自己搞了一套接口,其实就是一个Servlet
。
和遵循JSR356
标准的版本对比,有一个比较大的变化是,createWebSocketInbound
创建生命周期事件处理器StreamInbound
的时机是WebSocket
协议升级之前,此时还可以通过用户线程缓存(ThreadLocal等)的HttpServletRequest
对象,获取一些请求头等信息。
而遵循JSR356
标准的版本实现,创建生命周期事件处理的Endpoint
是在WebSocket
协议升级完成(经过HTTP握手)之后创建的,而WebSocket
握手成功给客户端响应101前,会结束销毁HttpServletRequest
对象,此时是获取不到请求头等信息的。
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
@WebServlet(urlPatterns = “/ws/test”)
public class MyWeSocketServlet extends WebSocketServlet {
@Override
protected StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request) {
MyMessageInbound messageInbound = new MyMessageInbound(subProtocol, request);
return messageInbound;
}
}
import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.WsOutbound;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
public class MyMessageInbound extends MessageInbound {
private String subProtocol;
private HttpServletRequest request;
public MyMessageInbound(String subProtocol, HttpServletRequest request) {
this.subProtocol = subProtocol;
this.request = request;
}
@Override
protected void onOpen(WsOutbound outbound) {
String msg = “connected, hello”;
ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
try {
outbound.writeBinaryMessage(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void onClose(int status) {
}
@Override
protected void onBinaryMessage(ByteBuffer byteBuffer) throws IOException {
// 接收到客户端信息
}
@Override
protected void onTextMessage(CharBuffer charBuffer) throws IOException {
// 接收到客户端信息
}
}
2、客户端实现
(1)前端js版
js版的客户端主要依托浏览器对WebScoket
的支持,在生命周期事件触发上和服务器端的差不多,这也应证了建立WebSocket
连接的两端是对等的。
编写WebSocke
t客户端需要注意以下几点:
-
和服务器端商议好传输的消息的格式,一般为json字符串,比较直观,编码解码都很简单,也可以是其他商定的格式。
-
需要心跳检测,定时给服务器端发送消息,保持连接正常。
-
正常关闭连接,即关闭浏览器窗口前主动关闭连接,以免服务器端抛异常。
-
如果因为异常断开连接,支持重连。
// 对websocket进行简单封装
WebSocketOption.prototype = {
// 创建websocket操作
createWebSocket: function () {
try {
if(‘WebSocket’ in window) {
this.ws = new WebSocket(this.wsUrl);
} else if(‘MozWebSocket’ in window) {
this.ws = new MozWebSocket(this.wsUrl);
} else {
alert(“您的浏览器不支持websocket协议,建议使用新版谷歌、火狐等浏览器,请勿使用IE10以下浏览器,360浏览器请使用极速模式,不要使用兼容模式!”);
}
this.lifeEventHandle();
} catch(e) {
this.reconnect(this.wsUrl);
console.log(e);
}
},
// 生命周期事件操作
lifeEventHandle: function() {
var self = this;
this.ws.onopen = function (event) {
self.connectCount = 1;
//心跳检测重置
if (self.heartCheck == null) {
self.heartCheck = new HeartCheckObj(self.ws);
}
self.sendMsg(5, “”)
self.heartCheck.reset().start();
console.log(“websocket连接成功!” + new Date().toUTCString());
};
this.ws.onclose = function (event) {
// 全部设置为初始值
self.heartCheck = null;
self.reconnect(self.wsUrl);
console.log(“websocket连接关闭!” + new Date().toUTCString());
};
this.ws.onerror = function () {
self.reconnect(self.wsUrl);
console.log(“websocket连接错误!”);
};
//如果获取到消息,心跳检测重置
this.ws.onmessage = function (event) {
//心跳检测重置
if (self.heartCheck == null) {
self.heartCheck = new HeartCheckObj(self.ws);
}
self.heartCheck.reset().start();
console.log(“websocket收到消息啦:” + event.data);
// 业务处理
// 接收到的消息可以放到localStorage里,然后在其他地方取出来
}
},
// 断线重连操作
reconnect: function() {
var self = this;
if (this.lockReconnect) return;
console.log(this.lockReconnect)
this.lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多,重连时间设置按倍数增加
setTimeout(function () {
self.createWebSocket(self.wsUrl);
self.lockReconnect = false;
self.connectCount++;
}, 10000 * (self.connectCount));
},
// 发送消息操作
sendMsg: function(cmd, data) {
var sendData = {“cmd”: cmd, “msg”: data};
try {
this.ws.send(JSON.stringify(sendData));
} catch(err) {
console.log(“发送数据失败, err=” + err)
}
},
// 关闭websocket接口操作
closeWs: function() {
this.ws.close();
}
}
/**
- 封装心跳检测对象
*/
function HeartCheckObj(ws) {
this.ws = ws;
// 心跳时间
this.timeout = 10000;
// 定时事件
this.timeoutObj = null;
// 自动断开事件
this.serverTimeoutObj = null;
}
HeartCheckObj.prototype = {
setWs: function(ws) {
this.ws = ws;
},
reset: function() {
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
// 开始心跳检测
start: function() {
var self = this;
this.timeoutObj = setTimeout(function() {
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
var ping = {“cmd”:1, “msg”: “ping”};
self.ws.send(JSON.stringify(ping));
//如果onmessage那里超过一定时间还没重置,说明后端主动断开了
self.serverTimeoutObj = setTimeout(function() {
//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
self.ws.close();
}, self.timeout)
}, self.timeout)
}
}
/**
-
-
创建websocket的主流程 *
-
*/
var currentDomain = document.domain;
var wsUrl = “ws://” + currentDomain + “/test”
var webSocketOption = new WebSocketOption(wsUrl)
webSocketOption.createWebSocket()
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function() {
webSocketOption.closeWs();
}
这里推荐一个在线测试WebSocket连接和发送消息的网站easyswoole.com/wstool.html:
真的很牛逼,很方便,很简单。还有源码github:https://github.com/easy-swoole/wstool,感兴趣可以看看。
(2)@ClientEndpoint注解方式
Java版客户端不用多说,把@ServerEndpoint
换成@ClientEndpoint
就可以了,其他都一样。@ClientEndpoint
比@ServerEndpoint
就少了一个value
,不需要设置URI。
@ClientEndpoint(encoders = {MessageEncoder.class}, decoders = {MessageDecoder.class})
public class WebSocketClientEndpoint {
private Session session;
@OnOpen
public void OnOpen(Session session) {
this.session = session;
Message message = new Message(0, “connecting…”);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
结尾
学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。
高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-LPQIME94-1712450142924)]
[外链图片转存中…(img-T2iq6epV-1712450142924)]
[外链图片转存中…(img-pReaspmX-1712450142925)]
[外链图片转存中…(img-rNPakiTR-1712450142925)]
[外链图片转存中…(img-SnRSnxry-1712450142925)]
[外链图片转存中…(img-iOhqc3Uq-1712450142925)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-0SzC0Mw3-1712450142926)]
结尾
学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。
高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-Bjk1Ne5H-1712450142926)]