feihe08 / hefei00.github.io Goto Github PK
View Code? Open in Web Editor NEWhefei‘s blog
hefei‘s blog
在javascript中,长除了null
和undefined
外,一切都是对象。每个对象都有一个属性链接到另一个对象,称为它的原型,该原型对象
也有自己的原型,直到遇到一个以null
为原型的对象。当试图访问一个对象上的属性时,它不仅在该对象上搜寻,还会搜寻该对象的原型,
以及该对象的原型的原型,以此层层向上搜索,直至找到该属性或者到达原型链的末尾。
注:
_proto_
属性,而不是prototype
。prototype
属性,普通对象没有改变子类的prototype,使其指向父类的一个实例
function Super() {
this.name = 'super';
this.arr = [1,2,3,4];
}
Super.prototype.sayName = function(){
console.log(this.name);
}
function Sub() {
this.age = 12;
}
Sub.prototype = new Super();//Sub继承Super
var test = new Sub();
console.log(test.age);//=> 12
console.log(test.name);//=> super
原型链:test -> Super的一个实例 -> Super.prototype
缺点:
Super
里的arr
Sub.prototype = {//一些方法}
,这样会导致原型链关系中断子类调用父类的构造函数
function Super() {
this.name = 'super';
this.arr = [1,2,3,4];
}
Super.prototype.sayName = function(){
console.log(this.name);
}
function Sub() {
Super.call(this);//调用父类构造函数
this.age = 12;
}
var test = new Sub();
console.log(test.age);//=> 12
console.log(test.name);//=> super
原型链: test -> Sub.prototype
缺点: 每个实例都会生成一遍自己的方法,无法复用
子类调用父类的构造函数继承属性,再通过改变prototype继承方法
function Super() {
this.name = 'super';
this.arr = [1,2,3,4];
}
Super.prototype.sayName = function(){
console.log(this.name);
}
function Sub() {
Super.call(this);//调用父类构造函数,继承属性,第二次调用Super
}
Sub.prototype = new Super();//继承方法, 第一次调用Super
var test = new Sub();
console.log(test.age);//=> 12
console.log(test.name);//=> super
原型链:test -> Super的一个实例 -> Super.prototype
缺点:组合式继承可以满足大部分需求,但是Super函数调用了两次,不适合一些对性能要求比较高的场景
创建一个让子类和父类原型链相连的方法
//一个构造函数
function create(o){
function F(){}
F.prototype = o;
return new F();
}
//父类
function Super() {
this.name = 'super';
this.arr = [1,2,3,4];
}
var test1 = create(Super);
test1.name = 'test1'
test1.arr.push('5');
var test2 = create(Super);
test2.name = 'test2';
test2.arr.push('6');
console.log(Super.arr);//=> [1,2,3,4,5,6]//被子类行为改变
注: es5中实现了一个类似的函数:Object.create
原型链: test1 -> Super
缺点: 原型中引用类型的变量会被所有实例共享
创建一个用于封装继承过程的函数,在该函数内部增强对象
//一个构造函数
function create(o){
function F(){}
F.prototype = o;
return new F();
}
//父类
function Super() {
this.name = 'super';
this.arr = [1,2,3,4];
}
function createObject(origin){
var clone = create(origin);
clone.sayName = function(){
console.log(this.name);
}
return clone;
}
var test = createObject(Super);
test.sayName();// =>'super'
test.arr.push(5);
console.log(Super.arr);//=>[1,2,3,4,5]
原型链: test -> Super
缺点: 原型中引用类型的变量会被所有实例共享
调用父类构造函数继承属性,再通过构造一个封装继承的函数,避免父类函数的二次调用
//一个构造函数
function create(o){
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(sub, super) {
var prototype = create(super.prototype); //创建对象
prototype.constructor = sub; //增强对象
sub.prototype = prototype; //指定对象
}
//父类
function Super() {
this.name = 'super';
this.arr = [1,2,3,4];
}
Super.prototype.sayName = function(){
console.log(this.name);
}
function Sub() {
Super.call(this);//调用父类构造函数
}
Sub.prototype.subSayName = function(){
console.log('sub' + this.name);
}
var test = new Sub();
组合寄生式继承是相对来说比较完美的方案
原型链: test -> prototype -> Super.prototype
一个CSS Module就是一个所有的class名和动画名被限定在本地“作用域”的csscss文件。
CSS Module内部通过ICSS来解决样式导入和导出的问题。分别对应:import
和:export
两个新增的伪类。
当在一个JS Module里导入一个CSS Module时,它会导出一个对象,这个对象里包含了所有的本地类名和全局类名。
/* style.css */
.green {
color: green;
}
import styles from "./style.css";
// import { style } from "./style.css";
element.innerHTML = '<div class="' + styles.green + '">';
CSS Module默认时局部作用域,按照上面的例子使用即可,局部作用域的样式只对引入了该样式的文件生效,对其他文件不生效。需要注意的是:由于局部calss名会被编译成一个hash字符串,所以使用时要使用style.green
,不能直接使用green
.
使用:global(.className)
的语法可以产生一个全局规则,这样的类名不会被便衣成hash字符串。
/* style.css */
:global(.green) {
color: green;
}
使用时:使用正常的class写法,就会引入全局class
import styles from "./style.css";
// import { style } from "./style.css";
element.innerHTML = '<div class="' + green + '">';
CSS Module中可以很方便的组合选择器。
.className {
color: green;
background: red;
}
//简单组合
.otherClassName {
composes: className;
color: yellow;
}
//多重组合
.otherClassName {
composes: classNameA classNameB;
color: yellow;
}
//引入依赖
.otherClassName {
composes: className from "./style.css";
}
reacr-router是一个react的一个声明式的路由解决方案。在学习react-router的过程中,官方的教程非常丰富。一边做下来,基本可以对react-router有个初步印象。这边文章是我在学习react-router时的一些纪录。
...
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>//导航至Home
<li><Link to="/about">About</Link></li>
<li><Link to="/topics">Topics</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</Router>
...
上面的代码中,出现了三个和react-router有关的自定义组件:Router
,Link
,Route
。它们本身就是react组件。上面代码的意思是:定义了一组路由(使用Router包裹),点击Link,渲染对应组件。更丰富的示例
<Router>
一共有三种模式:
<BrowserRouter>
:使用HTML5提供的history API保持UI和url同步<HashRouter>
:使用hash来保持UI和url同步,常用于不支持history API的浏览器<MemoryRouter>
:在内存中保存url纪录,用于非浏览器环境,比如测试环境或者叫react-native环境<NativeRouter>
:用于react-native<StaticRouter>
:静态router,从不改变地址在声明的时候常常这样写:
import {BrowserRouter as Router,} from 'react-router-dom'
router上可以设置一个basename,作为当前位置基准url,其子route都会带上这个基准url
<BrowserRouter basename="/calendar"/>
<Link to="/today"/> // 渲染为 <a href="/calendar/today">
为您的应用提供一个声明式的导航
<Link to="/courses" replace />
//导航至'/courses',replace表明替换掉原地址,如果为false,则在记录里添加一个新地址
使用<Redirect>
会导航到一个新的地址。
<Redirect push to="/somewhere/else"/>//使用push,表明新地址会加入纪录,而不是替换掉就纪录
当url匹配path时,渲染对应组件
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route path="/news" component={NewsFeed}/>
</div>
</Router>
//当地址是'/'
<div>
<Home/>
<!-- react-empty: 2 -->
</div>
//当地址是'/news'
<div>
<!-- react-empty: 1 -->
<NewsFeed/>
</div>
route的渲染方法有三种,如果三个属性同时存在,则优先级顺序为:component
,render
,children
route的props
<Route path>
如何与URL匹配的信息当web资源由其他域提供时,就会发起跨域http请求。处于安全考虑,浏览器会限制脚本中的跨域请求。使用XMLHttpRequest
和fetch
发起的请求必须遵守
同源策略,跨域的请求可以发起,但是响应会被浏览器拦截。这样的限制虽然安全,但是却很不方便。为了提高web应用的可用性,跨域资源共享机制(CROS)允许web服务器进行跨域访问控制。目前所有的浏览器都支持CROS。
XMLHttpRequest
和fetch
发起的请求跨域资源共享是通过一组新增的HTTP首部字段,让服务器声明哪些源站有权限访问哪些资源。规范要求,对那些可能对服务器产生副作用的请求,浏览器必须先使用
OPTIONS方法发起一个预检请求,询问服务器是否允许该跨域请求。服务器允许之后,才会发起实际的跨域请求。
如果一个请求满足以下条件,则称之为“简单请求”,简单请求不会触发CROS的预检请求。
javascript是一种动态类型语言。在javascript中,变量没有类型,值有类型。变量可以指代任何类型。
var foo = 42 //Number
var bar = 'abc' //String
在javascript中,一共有7种类型
Null
Undefined
Boolean
String
Number
Symbol
Object
原始类型的意味着值都是不可变的。例如对字符串的操作并不会改变原字符串,而是返回一个新的字符串。
原始类型只能代表数据,没有可执行的方法。但是在js中,我们可以见到例如var str = 'abcde'.substr(0,3)
这样的语句。
这是因为javascript引擎在执行这一句的时候,会自动为其包装上一个对应的String
对象,执行完这个substr
后,
再销毁这个对象。这一切都是自动完成的,就像原始类型也有方法一样。但其实相当于
var str
var temp = new String('abcde')
str = temp.substr(0,3)
destroy(temp)
这几种原始类型都有其对应的包装类型。
在js中,有一些对象是内置的。常见的内建对象列表如下
typeof
使用typeof
可以判断除了null
以外的类型。
typeof 32 //number
var a = {}
typeof a //object
var b = [1,2,3]
typeof b //object
typeof null //object. It's an bug in javascript
Object.prototype.toString.call
使用Object.prototype.toString.call
可以准确判定类型。
Object.prototype.toString.call(32) //[object Number]
var a = {}
Object.prototype.toString.call(a) //[object Object]
var b = [1,2,3]
Object.prototype.toString.call(b) //[object Array]
Object.prototype.toString.call(null) //[object Null]
将一种类型的值转换为另外一种类型叫做类型转换。在javascript里,有两种类型转化。一种是显示的,还有一种是隐式的。
undefined ----> 'undefined'
null ----> 'null'
true,false ----> 'true', 'false'
32 ----> '32'
'abc' ----> 'abc'
[1,2,3] ----> '1,2,3'
{a:1} ----> '[object Object]'
对象转换为字符串,规则如下:
toString
方法。如果返回原始类型的值,则对该值使用String
函数,不再进行后续步骤toString
返回的是对象,则执行对象的valueOf
方法,如果返回的是原始类型,则对该值使用String
方法,不再进行后续步骤valueOf
返回的是对象,则报错简单来说,对象会转换为[object Object]
undefined ----> NaN
null ----> 0
true,false ----> 1, 0
32 ----> 32
'abc' ----> NaN
‘’ ----> 0
'123abc' ----> NaN
[1,2,3] ----> NaN
{a:1} ----> NaN
对象转换为数值类型,规则如下:
valueOf
方法。如果返回原始类型的值,则对该值使用Number
函数,不再进行后续步骤valueOf
返回的是对象,则执行对象的toString
方法,如果返回的是原始类型,则对该值使用Number
方法,不再进行后续步骤toString
返回的是对象,则报错其他类型转换为布尔值,只有以下6种情况为false
,其余都换转换为true
。
显式类型转换主要是值使用String()
,Number()
,Boolean()
这三个构造函数来使其它类型的值转换为相应类型的值
var a = 42
String(a)//'42'
var b = '3.14'
Number(b)//3.14
Number('abc')//NaN NaN也是一种数字类型
var c = 'fa'
Boolean(c)//true
隐式转换是指在某些情况下一种类型会自动转换为另外一种类型
12 + 'ab'//'12ab'
12-'ab'//NaN
'5'-'2'//3
对于预期为字符串的地方,都会将非字符串转换为字符串。主要是二元+
运算符。
+
左侧或者右侧,都会将另外一个非字符串操作数转换为字符串'aaa' + 123//'aaa123'
123 + 'aaa'//'123aaa'
'aaa' + {a:2} //'aaa[object object]'
'aaa' + true 'aaatrue'
toString()
方法,然后再参与运算1 + []//'1'
5 * []//0
除了+
两侧都出现字符串时会把操作符转换为字符串,其它情况下都会把两侧运算符都转换为数值类型。
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5'*[] // 0
false/'5' // 0
'abc'-1 // NaN
if('acg'){
console.log(true)
}//true
+'43'//43
+'abc'//NaN
-'afa'//NaN
Preact是一个React的轻量化版本,大小仅有3kb,和React的API完全一致。Preact兼容React生态,同时提供了更小体积、更高性能。可以把Preact当作是使用es6写的极致优化版的React。
React作为一个流行的前端框架,了解其工作原理是有必要的。而Preact由于其小体积和es6的书写方式,阅读其代码是一个了解React很好的开始。
在研究Preact的源码之前,先得了解几个概念。首先就是Virtual—Dom。
前端领域中,和DOM打交道是少不了的。DOM的API繁多,Virtual—Dom就是对真实DOM的一种抽象:使用JS对象来代表DOM,只保留关键有用信息。
比如:
var vnode = {
nodeName: 'div',
attributes: {
className: 'container',
id: 'root'
},
children: 'this is container'
}
vnode
代表了一个虚拟node,对应的真实node是这样的
<div class="container" id="root">
this is container
</div>
可以看到,在我们写前端代码的时候,实际上用到的node属性是很少的。所以可以使用JS对象来模拟真实DOM,需要渲染的时候,再通过虚拟DOM生成真实DOM。
关于虚拟DOM更详细的内容,如有兴趣,可以参阅:
如何实现一个Virtual Dom(一)
how-to-write-your-own-virtual-dom
深度剖析:如何实现一个 Virtual DOM 算法
简洁清晰的virtual dom实现:snabbdom源码阅读 - 简书
上一节我们初步了解了Virtual—Dom,直接手写Virtual-Dom的效率比较低,而且容易出错。虽然可以通过一个构造函数来生成Virtual-Dom,比如:
var vnode = new Vnode('div', {id: 'root'}, children: 'this is container')
这样看起来还是不够直观,所以有了JSX,通过JSX,我们可以这样写Virtual-Dom。
var vnode = <div id='root'>this is container</div>
使用JSX解析器,可以将上面代码解析出Virtual-Dom的nodeName,attributes和children属性,并且调用一个vnode生成函数,的到一个和上文结果一样的vnode
注意:需要babel环境。
JSX语法可以让我们使用类似html的方式写Virtual-Dom,JSX本质上是一种语法糖,方便我们写组件模版。
关于JSX,这里有一篇很详细的解释文章:
wft-is-jsx
到了这里,我们终于要进入到Preact部分了。在[src/preact.js]( preact/preact.js at master · developit/preact · GitHub )中,定义了几个最顶层的方法。
在上面的方法中,我们现在主要关心h
和render
函数,h函数就是让Babel调用的helper函数,负责生成vnode。render函数负责将vnode渲染成真实node并且插入到对应的位置。
我们通过一个示例开始
// 告诉 Babel 将 JSX 转化成 h() 的函数调用,不然,Babel会使用默认的React的一个方法去构造vnode
/** @jsx h */
import { h, render } from 'preact';
render((
<div id="foo">
Hello, world!
</div>
), document.body);
render
函数位于src/render.js
,在执行render
函数的时候,JSX写法已经被编译成了vnode,此时可以直接把上面的render
函数等同于:
render({
nodeName: 'div',
attributes: undefined,
children: 'Hello, world!'
}, document.body);
在render
函数里,调用了diff函数去进行更新DOM的过程。diff
函数位于src/vdom/diff.js
。diff
函数大概如下:
//只保留整体逻辑,细节去掉很多
export function diff(dom, vnode, context, mountAll, parent, componentRoot) {
let ret = idiff(dom, vnode, context, mountAll);
// append the element if its a new parent
if (parent && ret.parentNode!==parent) parent.appendChild(ret);
return ret;
}
我们可以看到,diff
的主要功能非常清晰:内部调用了一个idiff
的方法去更新vnode,生成真实DOM节点,然后将其插入到对应节点上。而处理vnode的逻辑都在idiff
里面,
下面重点来看看这个函数。
//dom是老的dom节点,如果有的话。在上面的例子里,传递到此处的dom为undefined,我们现在只需关注第二个参数vnode即可
function idiff(dom, vnode, context, mountAll) {
let ref = vnode && vnode.attributes && vnode.attributes.ref;
//我们知道,react里有两种方式定义Component。一种是函数式的,一种是继承自`React.Component的。
//如果是函数式的,则执行该函数,得到一个vnode
while (isFunctionalComponent(vnode)) {
vnode = buildFunctionalComponent(vnode, context);
}
// 如果vnode未null,则此vnode代表一个空的textNode
if (vnode==null) vnode = '';
//针对vnode是一个字符串的特殊处理,更改老的textNode节点活着生成新的textNode节点
if (isString(vnode)) {
// update if it's already a Text node
if (dom && dom instanceof Text && dom.parentNode) {
if (dom.nodeValue!=vnode) {
dom.nodeValue = vnode;
}
}
else {
// it wasn't a Text node: replace it with one and recycle the old Element
if (dom) recollectNodeTree(dom);
dom = document.createTextNode(vnode);
}
return dom;
}
//如果vnode为一个继承自`React.Component`的Component。则通过Component的方法去比对新旧vnode差异,得到真实DOM
if (isFunction(vnode.nodeName)) {
return buildComponentFromVNode(dom, vnode, context, mountAll);
}
//下面就是一个Virtual-Dom的diff算法。这里不做过多解释。
...
}
上面我们的idiff
函数中,提到了函数式的Component和继承自React.Component的Component。下面我们来具体看一看区别:
var Hello1 = <div id="foo">Hello, world!</div>
编译后
var Hello1 = React.createElement(
"div",
{ id: "foo" },
"Hello, world!"
);
React.createElement函数为babel创建vnode的默认函数,作用和上文中的h
函数类似,后文不再特殊强调。
function Hello2 (){
return <div id="foo">Hello, world!</div>
}
编译后
function Hello2() {
return React.createElement(
"div",
{ id: "foo" },
"Hello, world!"
);
}
编译前
class Hello3 extends React.Component {
render(){
return <div id="hello">hello</div>
}
}
编译后
var _createClass = (function () {...})();
var _get = function get(_x, _x2, _x3) {...};
function _classCallCheck(instance, Constructor) { ...}
function _inherits(subClass, superClass) {...}
var Hello3 = (function (_React$Component) {
_inherits(Hello3, _React$Component);
function Hello3() {
_classCallCheck(this, Hello3);
_get(Object.getPrototypeOf(Hello3.prototype), "constructor", this).apply(this, arguments);
}
_createClass(Hello3, [{
key: "render",
value: function render() {
return React.createElement(
"div",
{ id: "hello" },
"hello"
);
}
}]);
return Hello3;
})(React.Component);
注:上面一些辅助函数内容过多,这里省略
通过以上的试验,我们可以看到,通过函数式组件和React.Component组件最后都是要生成vnode,他们的不同在于,函数式的组件可以理解为一个渲染函数,接受数据,然后渲染出对应的界面
它本身没有状态的,也没有生命周期方法。而React.Component组件自身可以有自己的state状态,也可以添加生命周期方法。在上面idiff
代码里,真对这两种不同的组件,会有不同的处理方式。对于函数式的组建,处理就很简单,直接调用就得到了vnode,而React.component组件在生成vnode时也会注册一些方法,添加一些hook。主要是setState
和一些生命周期方法。调用setState
会自动组件的render
函数。而改变组件的props并不会触发render
,类似Redux
的数据管理库都是自动将组件和数据绑定,监控数据变化,数据一变,就执行响应组建的render
函数。
关于HTTP协议,一篇就够了 - 简书
HTTP 协议 · 笔试面试知识整理
超文本传输协议 - 维基百科,自由的百科全书
HTTP协议是超文本传输协议(HyperText Transfer Protocol)的缩写,是客户端和浏览器之间的传输协议。通常,由客户端发起一个请求,建立一个到服务器指定端口(通常是80)的TCP链接。HTTP服务器监听到客户端的请求后,向客户端发送响应信息。
一个http请求格式如下:
请求行、请求头部、空行、请求数据
GET /562f25980001b1b106000338.jpg HTTP/1.1
Host img.mukewang.com
Accept image/webp,image/*,*/*;q=0.8
响应信息格式和请求信息格式相似:
状态行、消息报头、空行、响应正文
HTTP/1.1 200 OK
Server:Apache Tomcat/5.0.12
Content-Length:112
<html>...
通过在HTTP请求头部加入Connection: Keep-Alive
,后开启Keep-Alive模式。在改模式下,链接持续有效。当油后即请求时,能避免重建链接。
在HTTP1.1中,默认情况下所有链接都被保持。除非显示声明:Connection: close
才关闭
HTTP长链接不会一直保持,Keep-Alive:timeout=5, max=100
,持续5s,100次链接
document.createElement()//创建元素
document.createDocumentFragment()//创建文档碎片
someElement.cloneNode()//复制node
document.querySelector()//查找匹配的node,返回找到的第一饿或者null
document.querySelectorAll()//查找所有匹配的node,返回一个NodeList
document.getElementById()//根据id查找
document.getElementsByClassName()//根据class查找
document.getElementsByTagName()//根据标签名查找
document.getElementsByName()//根据元素的name属性查找
document.appendChild(child)//在末尾插入元素
document.removeChild(child)//删除子元素
document.replaceChild(newChild, oldChild)//替换子元素
document.insertBefore(newChild, olcChild)//在oldChild之前插入newChild
someElement.getAttribute(key)//获取属性
someElement.setAttribute(key, value)//设置属性
someElement.hasAttribute(key)//判断是否具有属性
自己之前学习react的时候只是跟着教程走了一遍,粗略的了解了一些概念。什么virtual-dom,单向数据流之类的。但是一致缺乏深入的实践。最近想搭建一个博客程序,实际演练一下。
如果要搭建一个完整的博客的话,要做很多的工作。数据存储和解析、图片的保存等等。在浏览react china论坛的时候,看到一种利用github issue作为数据源的方式。觉得这种方式
很实用,主要时解决了后端存储问题。前端只需要调用api获取数据,渲染数据就好了。
程序初始化的时候,会请求github的issues接口,数据请求完后,再初始化程序。
项目是利用create-react-app
搭建的,隐藏了很多配置,只需要写好业务代码。整个blog的框架比较简单,只有两个模块:首页和归档。主题是仿照hexo主题hexo-theme-apollo
。
markdown渲染使用了ReactMarkdown
。整个架构比较简单,拆分好逻辑之后,基本上就是拼接各个component
了。拆分好组件、复用起来页比较简单。项目结构如下:
blog
--src
--component
--header
--footer
--article
--title
--time
--...
--view
--index.js
--app.js
使用react,感受最深的是component的拆分。比如一个Article,要不要拆分出title和Body两个组件,组件如何复用等等。拆分好component之后,剩下的工作就是针对各个页面
拼接component了。
在考虑要不要使用redux或者mobx时,尝试了一下,感觉太繁琐。这个blog项目的结构也比较简单,没有复杂交互。所以就是使用了一个全局的对象去管理数据。
超文本传输协议(HTTP)是互联网使用的最广泛的网络协议。
HTTP是一个客户端终端(用户)请求到服务器上指定端口(默认为80)。我们称这个客户端为用户代理程序(user agent)。应答的服务器上村粗者一些资源。我们称这个应答服务器为源服务器(origin server)。在用户代理和源服务器中间可能存在多个“中间层”,比如代理服务器、网关或者隧道(tunnel)。
URL的全称是统一资源定位符,用来标示互联网上的资源地址。其格式如下:
协议/服务器/端口/路径/查询
http://www.aaa.com:8080/w/index.html?title=hahaha#name
GET /images/nav_logo242.png HTTP/1.1 请求行:包含方法、URL、版本
Host: www.google.com.hk 请求头部:指明服务器要使用的附加信息
User-Agent: Mozilla/5.0
空行,不可以省略
some data 请求数据:可为空
HTTP/1.1 200 OK 状态行:版本、状态码、状态消息
Content-Length: 16786 响应头部:客户端摇使用的附加信息
Content-Type: image/png
空行,不可省略
some data 响应正文
详细头部字段表: HTTP头字段列表 - 维基百科,自由的百科全书
1xx ——--消息——请求已被服务器接收,继续处理
2xx———-成功——请求已成功被服务器接收、理解、并接受
3xx———-重定向——需要后续操作才能完成这一请求
4xx———-请求错误——请求含有词法错误或者无法被执行
5xx———-服务器错误——服务器在处理某个正确请求时发生错误
详细状态码说明见: HTTP状态码 - 维基百科,自由的百科全书
HTTP0.9只有GET
一种方法,且不支持请求头。所以无法和服务器进行太多交互。
HTTP1新增了POST
和HEAD
方法,支持请求头,状态码等,缓存等。类似于上文的例子。
在HTTP1.0版本,默认是短链接,即每个TCP只能发送一个请求。一次请求完毕之后,就会断开链接。下一次请求会重新建立一个TP链接。为了解决这个问题,有些浏览器请求时,会在请求头部加一个非标准的Connection
字段。
Connection: keep-alive
这个字段告诉服务器不要关闭TCP链接,以便于其他请求复用。
HTTP1.1相比于1.0,主要是将1.0标准化并加入了很多改进。其中最重要的有:
Connection:keep-alive
时默认的。需要注意的是,在一个TCP链接里面,可以发出多个请求,但是响应还是有顺序的。服务器只有处理完前一个响应之后,才会去处理第二个响应。如果前面有一个响应特别慢,后面的响应就得排队等候。为了解决这个问题,要么减少请求,要么多开持久链接。这也就是在网页优化时,经常会提到合并脚本和样式(可以减少请求)和域名分片(同一个域名下可以开的TCP链接时又限制的,一般为6)。
HTTP2完全兼容HTTP1.1的语义。并且实现了多路复用、头信息压缩、服务器推送等。
缓存是一种可以有效提高用户体验的手段。关于缓存控制,google developer网站有篇很好的教程:HTTP 缓存 -Google Developers
一次完整的HTTP请求与响应涉及了哪些知识? - SegmentFault
在git中,整合不同分支的代码呦两种方法:merge
和rebase
。
本文示意图都来自于git
如下图,experiment分支是在c2提交从master分支产生。在这之后,experiment分支和master分支都有新的提交。现在需要将两次提交整合在一起。
最简单的办法是merge
git checkout master
git merge experiment
产生的提交记录如下
merge
操作简便快捷,但是这样做每次都会产生一个merge操作的纪录,在文件改动特别频繁的时候,提交记录会比较乱。
git rebase master
产生的提交记录如下
可以看到,提交记录干净了许多。
git rebase虽然可以得到干净的git纪录,但是它并不是一个安全的操作,相比于merge的方式更容易产生文件冲突。产生冲突的时候,rebase过程会中止。
需要手动解决冲突。
git add .
git rebase --containue
此外,git rebase还有这两条命令
git rebase --abort 取消此次rebase
git rebase --skip 跳过当前补丁
由于历史原因,js在设计时并没有考虑到模块化的需求。后来随着业务逻辑的前移,前端的逻辑越来越重,代码也越来越多。
所以必须有一种模块化方案提升代码的可维护性和复用性。
最开始的模块化是利用闭包的特性去实现的
var module1 = (function () {
var name = 'fdasf'
return {
sayName: function () {
console.log(name)
}
//...
}
})()
module1
倍挂载在全局变量,变量a
对于全局是不可见的,只能通过闭包函数访问。但是这样本质上还是污染了全局变量,随着代码的增加,命名冲突还是会出现,只能手工管理这些模块。
node开始流行后,迫切需要一种模块化方案。CommonJs社区推出了commonjs规范。简要介绍如下:
require
。该函数接受一个模块定义,返回模块暴露出的api一个官方的例子如下
//math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
//increment.js
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};
//program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2
一个commonjs模块里,需要暴露的api绑定在exports对象上,像使用该模块的时候,使用require
函数导入模块,这个函数返回该模块的exports对象。可以
简单的理解为:require
函数读取文件并执行文件,然后返回该文件的exports对象。
需要注意的是:
exports
对象不能被赋予其他值。可以理解为:exports = module.exports = {}
。可以看到,exports
只是module.exports
的一个索引,module.exports
,所以对于exports
的操作要小心。不可赋予新值。commonjs规范主要用于服务器端,但是在浏览器端没法直接使用。这是因为:
function
的包裹,变量暴露在全局对象上。为了解决浏览器端模块化的问题,社区提出了AMD规范:
存在一个全局函数define
。该函数如下:
define(id?, dependencies?, factory)
其中:
一个典型的例子如下:
define(['a','b','c'], function (a, b, c) {//a,b,c分别对应几个模块的输出值
...
})
可以看出,在一个amd模块里,一个模块使用到的依赖需要提前被声明,而不像commonjs一样可以需要时再引入。
在amd模块里,依赖是被提前下载并执行的,如果依赖很多的话,一些暂时用不到的依赖也会提前被下载执行,会带来一定的性能损耗。虽然amd模块里也可以
不提前声明依赖,等到需要的时候使用require
来引入,但是这样会带来另一个问题:后续代码的执行必须等到依赖被下载执行完后才能进行。所以后来又提出了cmd规范,和amd的主要区别是:cmd模块的依赖是预先下载,需要的时候再执行。
在es之前,js的模块化主要有两种方案:commonjs和amd,分别应用于服务器和浏览器。在es6里,从语言标准层面实现了模块功能。在es6之前,由于语言的
不支持,各种模块化方案都是运行时加载,而es6的模块化,在编译时就确定了模块的依赖关系和输入输出变量。和commonjs不同的是es6的模块输出的始终是值的引用,而不是值的复制。关于es6的module语法,推荐阮一峰:module
a test issue created by api
在Elements选项下,可以看到Elements面板内容。左侧为结构化html文档,右侧为样式、计算后样式等。
右键任意一节点,可以对该节点进行操作,包括修改、删除等。在右侧,可以编辑对应节点的样式,也可以切换到Computed选项,查看计算后的样式。
console面板主要提供一个执行脚本、显示脚本输入输出的环境。
打开chrome的开发者工具,切换到network选项下,就可以看到chrome的网络面板
上面是功能区域,下面是网络请求内容。
功能区域从左到右依次为:
功能区域之下的很多行就是具体的网络请求了。在每一行里,显示了该网络请求大大致信息:名称、请求状态、类型、发出请求的页面、大小、请求耗时、瀑布图等。
点击名称,会显示该请求的详情。
从左到右,依次是:
Sources面板主要是调试用的。最左侧是资源文件结构,中间是代码区域,可以设置断点、修改脚本,右侧上方是调试功能区域,下一步、进入函数、推出函数等功能。右侧下方展示了当前调用栈、当前作用域等信息,还可以添加watch变量,实时监听变量的变化。
Audits面板可以分析页面载入速度,然后提供优化建议。点击run按钮,就可以对页面进行分析,分析结果会显示页面有哪些可以优化的地方
Security面板显示网站安全信息、证书等。
Application面板展示了页面使用的所有资源,包括存储、数据库、缓存等。在次面板下,可以修改本地存储、会话存储,可以查看cookie,操作web sql等。
关于performance和Memory,由于自己使用的不适很熟练,建议直接查看chrome官方的开发者文档
BOM主要处理浏览器窗口和框架。用来处理除了网页文档内容、和浏览器本身的交互。
BOM的核心是Window对象,它代表浏览器的一个实例。在浏览器中,window即使js访问浏览器窗口的一个接口,又是EVCMAScript规定的Global对象,所有在全局作用域中声明的变量、函数都会变成window的属性和方法。
每个框架都有一个window对象
top
: 最顶层框架
parent
:当前框架父框架
self
:window
frames
: 子框架
setTimeout
setInterval
parseInt
parseFloat
location对象提供了当前加载文档的信息,也又一些导航功能。修改location的属性(除了hash)都会改变当前加载的页面,并在浏览器历史记录中增加一条。
hash
,host
,hostname
,href
,pathname
,port
,protocol
,search
assgin
:打开一个新的url,增加历史记录
replace
: 打开一个url,不会生成历史记录
reload
:重载当前页面
表示浏览器信息
标明客户端的能力,主要指像素尺寸
历史记录相关,处于安全策略,开发人员只能获取历史记录长度,而不能知道历史记录的具体url
a test issue created by api
迭代器是对数据进行遍历操作的一种模式。在es6之前,遍历数据主要有for(..)
,forEach
,for...in
三种方式。其中for
循环和forEach
都只用于数组,for
循环写起来繁琐,forEach
不支持break
。for...in
循环用来遍历对象的可枚举属性,包括继承来的属性。主要缺点在于必须使用hasOwnProperty
来排除原型链上属性,而且for...in
循环的顺序是不确定的。
es6里,增加了Map和Set,这样一共就有Object,Array,Map,Set四种表示集合的结构。迭代器就是为不同的数据结构提供统一访问接口的一个方式。为了变成可迭代对象,一个对象必须实现@@Symbol.iterator
属性。该属性是一个函数,执行该函数,就会得到一个迭代对象。
var arr = [1,2,3];
var it = arr[Symbol.iterator]();
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { value: undefined, done: true }
当使用for...of
循环时,会自动寻找迭代器借口,不用我们像上面一样自己去调用。
var arr = [1,2,3];
for(var item of arr){
console.log(item)//1,2,3
}
在es6中,原生部署迭代器接口的有数组、某些类数组对象(比如String)、Set、Map。一般对象如果想要实现迭代器,可以自己部署此接口。
浏览器的主要功能是向服务器发出请求,在浏览器窗口展示所选择的资源。这里的资源一般是指HTML文档,pdf,图片等。资源的位置由URI(统一资源标识符)指定。
浏览器主要组件为:
呈现引擎的主要作用是解析并渲染网页内容。
[image:70BEC576-5D17-4DBB-A82F-4B13E7DCB9B7-34261-00017BC4AE234561/50EA94E2-A1AD-4D66-AAC7-5ED1968DF414.png]
基本流程为:
解析html,生成dom树;解析css,生成呈现树;布局;绘制。
参考:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.