Coder Social home page Coder Social logo

imooc's People

Contributors

cruxf avatar zwm1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

imooc's Issues

jQuery基础知识

前言

本篇博文是记录jQuery的基础知识,根据慕课网的教程整理而成。还有,为了优化大家的阅读体验,强烈建议先安装Chrome浏览器的插件——GayHub。下载安装地址

jQuery样式篇

第一个例子

不多说,这是我们踏入编程界输出的第一句话,下面贴上代码(默认导入jQuery源文件):

<div></div>
<script>
  $(document).ready(function () {
    $("div").html("hello world");
  })
</script>

【代码解析】
$(document).ready 的作用是等页面的文档(document)中的节点都加载完毕后,再执行后续的代码,因为我们在执行代码的时候,可能会依赖页面的某一个元素,我们要确保这个元素真正的的被加载完毕后才能正确的使用。

jQuery对象与DOM对象

jQuery对象与DOM对象是不一样的,大概就是:jQuery是一个类数组对象,而DOM对象就是一个单独的DOM元素。具体有以下区别:
(1)通过jQuery方法包装后的对象,是一个类数组对象。它与DOM对象完全不同,唯一相似的是它们都能操作DOM。
(2)通过jQuery处理DOM的操作,可以让开发者更专注业务逻辑的开发,而不需要我们具体知道哪个DOM节点有那些方法,也不需要关心不同浏览器的兼容性问题,我们通过jQuery提供的API进行开发,代码也会更加精短。

jQuery对象与DOM对象之间的转换

(1)jQuery => DOM

第一种方式
var $div = $('div') //jQuery对象
var div = $div[0] //转化成DOM对象
div.style.color = 'red' //操作dom对象的属性
第二种方式
var $div = $('div') //jQuery对象
var div = $div.get(0) //通过get方法,转化成DOM对象
div.style.color = 'red' //操作dom对象的属性

(2)DOM => jQuery

var div = document.getElementsByTagName('div'); //dom对象
var $div = $(div); //jQuery对象
var $first = $div.first(); //找到第一个div元素
$first.css('color', 'red'); //给第一个元素设置颜色

jQuery选择器

(1)ID选择器:$( "#id" )
(2)类选择器:$( ".class" )
(3)元素选择器:$( "element" )
(4)全选择器:$( "*" )
(5)层级选择器

  • $("parent > child"):子选择器,选择所有指定"parent"元素中指定的"child"的直接子元素;
  • $("anestor descendant"):后代选择器,选择给定的祖先元素的所有后代元素,一个元素的后代可能是该元素的一个孩子、孙子、曾孙等;
  • $("prev + next"):相邻兄弟选择器,选择所有紧接在"prev"元素后的"next"元素;
  • $("prev ~ siblings"):一般选择器,匹配"prev"元素之后的所有兄弟元素,具有相同的父元素,并匹配过滤"siblings"选择器。

(6)基本筛选选择器

  • $(":first"):匹配第一个元素;
  • $(":last"):匹配最后一个元素;
  • $(":header"):选择所有的标题元素,像h1、h2等;
  • $(":root"):选择该文档的根元素;
  • $(":animated"):选择所有正在执行动画效果的元素;
  • $(":not(selector)"):一个用来过滤的选择器,选择所有元素去除不匹配给定的选择器元素;
  • $(":eq(index)"):在匹配的集合中选择索引值为index的元素;
  • $(":gt(index)"):在匹配的集合中所有大于给定index(索引值)的元素;
  • $(":lt(index)"):在匹配的集合中所有小于给定index(索引值)的元素;
  • $(":even"):选择索引值为偶数的元素,从0开始计数;
  • $(":odd"):选择索引值为奇数的元素,从0开始计数;
  • $(":lang(language)"):选择指定语言的所有元素;

(7)内容筛选选择器

  • $(":contains(text)"):选择所有包含指定文本的元素;
  • $(":parent"):选择所有含有子元素或者文本的元素;
  • $(":empty"):选择所有没有子元素的元素,包含文本节点;
  • $(":has(selector)"):选择元素中至少包含指定选择器的元素;

(8)可见性筛选选择器

  • $(":visible"):选择所有显示的元素;
  • $(":hidden"):选择所有隐藏的元素;

(9)属性筛选选择器

(10)子元素筛选选择器

(11)表单元素选择器

(12)表单对象属性筛选选择器

(13)特殊选择器this

  • this是JavaScript中的关键字,指的是当前的上下文对象,简单的说就是方法/属性的所有者。在JavaScript中this是动态的,也就是说这个上下文对象都是可以被动态改变的(可以通过call,apply等方法)。
  • 总结1:this,表示当前的上下文对象是一个html对象,可以调用html对象所拥有的属性和方法。
  • 总结2:$(this),代表的上下文对象是一个jquery的上下文对象,可以调用jQuery的方法和属性值。
$('p').click(function(){
    //把p元素转化成jQuery的对象
    var $this= $(this) 
    $this.css('color','red')
})

jQuery中attr()和removeAttr()

(1)jQuery中用attr()方法来获取和设置元素属性,attr是attribute(属性)的缩写,在jQuery DOM操作中会经常用到attr()

  • attr(传入属性名):获取属性的值
//找到第二个input,通过attr获取属性value的值
$("input:eq(1)").attr('value')
  • attr(属性名, 属性值):设置属性的值
//找到第一个input,通过attr设置属性value的值
$("input:first").attr('value', '.attr( attributeName, value )')
  • attr(属性名,函数值):设置属性的函数值
//找到第三个input,通过使用一个函数来设置属性
//可以根据该元素上的其它属性值返回最终所需的属性值
//例如,我们可以把新的值与现有的值联系在一起:
$("input:eq(2)").attr('value', function (i, val) {
  return '通过function设置' + val
})

(2)removeAttr( attributeName ) : 为匹配的元素集合中的每个元素中移除一个属性(attribute)

//找到第四个input,通过使用removeAttr删除属性
$("input:eq(3)").removeAttr('value')

jQuery中html()和text()

(1)html()方法:获取集合中第一个匹配元素的HTML内容 或 设置每一个匹配元素的html内容,具体有3种用法

  • html() 不传入值,就是获取集合中第一个匹配元素的HTML内容
  • html( htmlString ) 设置每一个匹配元素的html内容
<div class="left first-div">
  <div class="box">
    <a>:first-child</a>
    <a>第二个元素</a>
    <a>:last-child</a>
  </div>
</div>

<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script>
	$(document).ready(function(){
		$(".box a:first").html("替换元素");
	})
</script>
  • html( function(index, oldhtml) ) 用来返回设置HTML内容的一个函数
  • 【注意】:html()方法内部使用的是DOM的innerHTML属性来处理的,所以在设置与获取上需要注意的一个最重要的问题,这个操作是针对整个HTML内容(不仅仅只是文本内容)

(2)text()方法:得到匹配元素集合中每个元素的文本内容结合,包括他们的后代,或设置匹配元素集合中每个元素的文本内容为指定的文本内容,具体有3种用法:

  • text() 得到匹配元素集合中每个元素的合并文本,包括他们的后代;
  • text( textString ) 用于设置匹配元素内容的文本;
  • text( function(index, text) ) 用来返回设置文本内容的一个函数;
  • 【注意】text()结果返回一个字符串,包含所有匹配元素的合并文本

(3)html()和text()的异同

  • .html与.text的方法操作是一样,只是在具体针对处理对象不同;
  • .html处理的是元素内容,.text处理的是文本内容;
  • .html只能使用在HTML文档中,.text 在XML 和 HTML 文档中都能使用;
  • 如果处理的对象只有一个子文本节点,那么html处理的结果与text是一样的
  • 火狐不支持innerText属性,用了类似的textContent属性,.text()方法综合了2个属性的支持,所以可以兼容所有浏览器

jQuery中的val()方法

jQuery中有一个.val()方法主要是用于处理表单元素的值,比如 input, select 和 textarea:

  • val()无参数,获取匹配的元素集合中第一个元素的当前值;
  • val( value ),设置匹配的元素集合中每个元素的值;
  • val( function ) ,一个用来返回设置值的函数。

jQuery中的addClass()方法

jQuery增加了一个.addClass()方法,用于动态增加class类名:

  • addClass( className ) : 为每个匹配元素所要增加的一个或多个样式名;
  • addClass( function(index, currentClass) ) : 这个函数返回一个或更多用空格隔开的要增加的样式名;
  • 【注意事项】.addClass()方法不会替换一个样式类名。它只是简单的添加一个样式类名到元素上。

jQuery中的removeClass()方法

jQuery通过.addClass()方法可以很便捷的增加样式。如果需要样式之间的切换,同样jQuery提供了一个很方便的.removeClass(),它的作用是从匹配的元素中删除全部或者指定的class:

  • removeClass( [className ] ):每个匹配元素移除的一个或多个用空格隔开的样式名;
  • removeClass( function(index, class) ) : 一个函数,返回一个或多个将要被移除的样式名

jQuery中的toggleClass()方法

jQuery通过toggleClass方法动态添加删除Class,一次执行相当于addClass,再次执行相当于removeClass:

  • toggleClass( className ):在匹配的元素集合中的每个元素上用来切换的一个或多个(用空格隔开)样式类名;
  • toggleClass( className, switch ):一个布尔值,用于判断样式是否应该被添加或移除;
  • toggleClass( [switch ] ):一个用来判断样式类添加还是移除的 布尔值;
  • toggleClass( function(index, class, switch) [, switch ] ):用来返回在匹配的元素集合中的每个元素上用来切换的样式类名的一个函数,接收元素的索引位置和元素旧的样式类作为参数。

jQuery中的css()方法

在jQuery中我们要动态的修改style属性我们只要使用css()方法就可以实现了:

  • css(propertyName, value ):设置CSS属性和值
//多种写法设置颜色
$('.fourth').css("background-color","red");
$('.fifth').css("backgroundColor","yellow");
  • css( propertyName, function ):可以传入一个回调函数,返回取到对应的值进行处理
//获取到指定元素的宽度,在回调返回宽度值
//通过处理这个value,重新设置新的宽度
$('.sixth').css("width",function(index,value){
  value = value.split('px');
  return (Number(value[0])+50)+value[1];
})
  • css( properties ):可以传一个对象,同时设置多个样式
//合并设置,通过对象传设置多个样式
$('.seventh').css({
  "font-size":"15px",
  "border":"1px solid red"
})

jQuery DOM篇

JavaScript创建DOM

(1)创建流程如下

  • 创建节点(常见的:元素、属性和文本);
  • 添加节点的一些属性;
  • 加入到文档中

(2)涉及到一些方法

  • 创建元素:document.createElement;
  • 设置属性:setAttribute;
  • 添加文本:innerHTML;
  • 加入文档:appendChild

(3)存在的问题

  • 每一个元素节点都必须单独创建;
  • 节点是属性需要单独设置,而且设置的接口不是很统一;
  • 添加到指定的元素位置不灵活;
  • 最后还有一个最重要的:浏览器兼容问题处理

jQuery节点创建与属性的处理

同时创建元素节点、文本节点、属性节点

var $body = $('body');
$body.on('click', function () {
  //通过jQuery生成div元素节点
  var div = $("<div class='right'><div class='aaron'>动态创建DIV元素节点</div></div>")
  $body.append(div)
})

DOM内部插入append()与appendTo()

(1)append()前面是被插入的对象,后面是要在对象内插入的元素内容;
(2)appendTo()前面是要插入的元素内容,而后面是被插入的对象

DOM外部插入after()与before()

before与after都是用来对相对选中元素外部增加相邻的兄弟节点。

$("#bt1").on('click', function () {
  //在匹配test1元素集合中的每个元素前面插入p元素
  $(".test1").before('<p>before,在匹配元素之前增加</p>', '<p>多参数</p>')
})
$("#bt2").on('click', function () {
  //在匹配test1元素集合中的每个元素后面插入p元素
  $(".test2").after('<p>after,在匹配元素之后增加</p>', '<p>多参数</p>')
})

DOM内部插入prepend()与prependTo()

在元素内部进行操作的方法,除了在被选元素的结尾(仍然在内部)通过append与appendTo插入指定内容外,相应的还可以在被选元素之前插入,jQuery提供的方法是prepend与prependTo

这里总结下内部操作四个方法的区别:

  • append()向每个匹配的元素内部追加内容
  • prepend()向每个匹配的元素内部前置内容
  • appendTo()把所有匹配的元素追加到另一个指定元素的集合中
  • prependTo()把所有匹配的元素前置到另一个指定的元素集合中

DOM外部插入insertAfter()与insertBefore()

与内部插入处理一样,jQuery由于内容目标的位置不同,然增加了2个新的方法insertAfter与insertBefore

  • .before()和.insertBefore()实现同样的功能。主要的区别是语法——内容和目标的位置。 对于before()选择表达式在函数前面,内容作为参数,而.insertBefore()刚好相反,内容在方法前面,它将被放在参数里元素的前面
  • .after()和.insertAfter() 实现同样的功能。主要的不同是语法——特别是(插入)内容和目标的位置。 对于after()选择表达式在函数的前面,参数是将要插入的内容。对于 .insertAfter(), 刚好相反,内容在方法前面,它将被放在参数里元素的后面
  • before、after与insertBefore。insertAfter的除了目标与位置的不同外,后面的不支持多参数处理

DOM节点删除之empty()的基本用法

(1)empty 顾名思义,清空方法,但是与删除又有点不一样,因为它只移除了 指定元素中的所有子节点。
(2)如果我们通过empty方法移除里面div的所有元素,它只是清空内部的html代码,但是标记仍然留在DOM中。

<div class="hello"><p>慕课网</p></div>
//通过empty处理
$('.hello').empty()

//结果:<p>慕课网</p>被移除
<div class="hello"></div>

DOM节点删除之remove()的有参用法和无参用法

(1)remove与empty一样,都是移除元素的方法,但是remove会将元素自身移除,同时也会移除元素内部的一切,包括绑定的事件及与该元素相关的jQuery数据。
(2)为了防止"内存泄漏",所以前端开发者一定要注意,绑了多少事件,不用的时候一定要记得销毁
(3)通过remove方法移除div及其内部所有元素,remove内部会自动操作事件销毁方法,所以使用使用起来非常简单

<div class="hello"><p>慕课网</p></div>

//通过remove处理
$('.hello').remove()
//结果:
<div class="hello"><p>慕课网</p></div> 全部被移除
//节点不存在了,同事事件也会被销毁

DOM节点删除之empty和remove区别

(1)要用到移除指定元素的时候,jQuery提供了empty()与remove([expr])二个方法,两个都是删除元素,但是两者还是有区别
(2)empty方法

  • 严格地讲,empty()方法并不是删除节点,而是清空节点,它能清空元素中的所有后代节点
  • empty不能删除自己本身这个节点
    (3)remove方法
  • 该节点与该节点所包含的所有后代节点将同时被删除
  • 提供传递一个筛选的表达式,删除指定合集中的元素

DOM节点删除之保留数据的删除操作detach()

(1)如果我们希望临时删除页面上的节点,但是又不希望节点上的数据与事件丢失,并且能在下一个时间段让这个删除的节点显示到页面,这时候就可以使用detach方法来处理
(2)detach从字面上就很容易理解。让一个web元素托管。即从当前页面中移除该元素,但保留这个元素的内存模型对象。

DOM节点删除之detach()和remove()区别

(1)remove:移除节点

  • 无参数,移除自身整个节点以及该节点的内部的所有节点,包括节点上事件与数据
  • 有参数,移除筛选出的节点以及该节点的内部的所有节点,包括节点上事件与数据
    (2)detach:移除节点
  • 移除的处理与remove一致
  • 与remove()不同的是,所有绑定的事件、附加的数据等都会保留下来
  • 例如:$("p").detach()这一句会移除对象,仅仅是显示效果没有了,但是内存中还是存在的。当你append之后,又重新回到了文档流中,就又显示出来了。

DOM拷贝clone()

(1).clone()方法深度 复制所有匹配的元素集合,包括所有匹配元素、匹配元素的下级元素、文字节点。
(2)clone方法比较简单就是克隆节点,但是需要注意,如果节点有事件或者数据之类的其他处理,我们需要通过clone(ture)传递一个布尔值ture用来指定,这样不仅仅只是克隆单纯的节点结构,还要把附带的事件与数据给一并克隆了

HTML部分
<div></div>

JavaScript部分
$("div").on('click', function() {//执行操作})

//clone处理一
$("div").clone()   //只克隆了结构,事件丢失
//clone处理二
$("div").clone(true) //结构、事件与数据都克隆

(3)细节

  • clone()方法时,在将它插入到文档之前,我们可以修改克隆后的元素或者元素内容,如 $(this).clone().css('color','red') 增加了一个颜色
  • 通过传递true,将所有绑定在原始元素上的事件处理函数复制到克隆元素上
  • clone()方法是jQuery扩展的,只能处理通过jQuery绑定的事件与数据
  • 元素数据(data)内对象和数组不会被复制,将继续被克隆元素和原始元素共享。深复制的所有数据,需要手动复制每一个

DOM替换replaceWith()和replaceAll()

(1).replaceWith( newContent ):用提供的内容替换集合中所有匹配的元素并且返回被删除元素的集合

<div>
    <p>第一段</p>
    <p>第二段</p>
    <p>第三段</p>
</div>
$("p:eq(1)").replaceWith('<a style="color:red">替换第二段的内容</a>')

(2).replaceAll( target ) :用集合的匹配元素替换每个目标元素。.replaceAll()和.replaceWith()功能类似,但是目标和源相反,用上述的HTML结构,我们用replaceAll处理
$('<a style="color:red">替换第二段的内容</a>').replaceAll('p:eq(1)')
(3)总结

  • .replaceAll()和.replaceWith()功能类似,主要是目标和源的位置区别
  • .replaceWith()与.replaceAll() 方法会删除与节点相关联的所有数据和事件处理程序
  • .replaceWith()方法,和大部分其他jQuery方法一样,返回jQuery对象,所以可以和其他方法链接使用
  • .replaceWith()方法返回的jQuery对象引用的是替换前的节点,而不是通过replaceWith/replaceAll方法替换后的节点

DOM包裹wrap()方法

(1).wrap( wrappingElement ):在集合中匹配的每个元素周围包裹一个HTML结构

<p>p元素</p>
$('p').wrap('<div></div>')
//最后结果
<div>
    <p>p元素</p>
</div>

(1).wrap( function ) :一个回调函数,返回用于包裹匹配元素的 HTML 内容或 jQuery 对象

$('p').wrap(function() {
    return '<div></div>';   //与第一种类似,只是写法不一样
})

DOM包裹unwrap()方法

(1)jQuery提供了一个unwrap()方法 ,作用与wrap方法是相反的。将匹配元素集合的父级元素删除,保留自身(和兄弟元素,如果存在)在原来的位置。

p元素

$('p').unwrap(); //最后结果

p元素

DOM包裹wrapAll()方法

(1)wrap是针对单个dom元素处理,如果要将集合中的元素用其他元素包裹起来,也就是给他们增加一个父元素,针对这样的处理,JQuery提供了一个wrapAll方法
(2).wrapAll( wrappingElement ):给集合中匹配的元素增加一个外面包裹HTML结构

<p>p元素</p>
<p>p元素</p>
$('p').wrapAll('<div></div>')
//最后结果
<div>
    <p>p元素</p>
    <p>p元素</p>
</div>

(3).wrapAll( function ) :一个回调函数,返回用于包裹匹配元素的 HTML 内容或 jQuery 对象,通过回调的方式可以单独处理每一个元素

<p>p元素</p>
<p>p元素</p>
$('p').wrapAll(function() {
    return '<div><div/>'; 
})
//最后结果
<div>
    <p>p元素</p>
</div>
<div>
    <p>p元素</p>
</div>

DOM包裹wrapInner()方法

(1).wrapInner( wrappingElement ):给集合中匹配的元素的内部,增加包裹的HTML结构

<div>p元素</div>
<div>p元素</div>
$('div').wrapInner('<p></p>')
//最后结果
<div>
    <p>p元素</p>
</div>
<div>
    <p>p元素</p>
</div>

(2).wrapInner( function ) :允许我们用一个callback函数做参数,每次遇到匹配元素时,该函数被执行,返回一个DOM元素,jQuery对象,或者HTML片段,用来包住匹配元素的内容

<div>p元素</div>
<div>p元素</div>
$('div').wrapInner(function() {
    return '<p></p>'; 
})
//最后结果
<div>
    <p>p元素</p>
</div>
<div>
    <p>p元素</p>
</div>

jQuery遍历之children()方法

(1)jQuery是一个合集对象,如果想快速查找合集里面的第一级子元素,此时可以用children()方法。这里需要注意:.children(selector) 方法是返回匹配元素集合中每个元素的所有子元素(仅儿子辈,这里可以理解为就是父亲-儿子的关系)

$("#bt1").click(function() {
  //找到所有class=div的元素节点,然后找到其对应的子元素,并且加上一个红色边框
  $('.div').children().css('border','3px solid red')
})

$("#bt2").click(function() {
  //找到所有class=div的元素
 //找到其对应的子元素,然后筛选出最后一个,给边宽加上颜色
 $('.div').children(':last').css('border', '3px solid blue')
})

jQuery遍历之find()方法

(1)jQuery是一个合集对象,如果想快速查找DOM树中的这些元素的后代元素,此时可以用find()方法,这也是开发使用频率很高的方法。这里要注意 children与find方法的区别,children是父子关系查找,find是后代关系(包含父子关系)

<div class="div">
    <ul class="son">
        <li class="grandson">1</li>
    </ul>
</div>
代码如果是$("div").find("li"),此时,li与div是祖辈关系,通过find方法就可以快速的查找到了。

(2).find()方法要注意的知识点:

  • find是遍历当前元素集合中每个元素的后代。只要符合,不管是儿子辈,孙子辈都可以。
  • 与其他的树遍历方法不同,选择器表达式对于 .find() 是必需的参数。如果我们需要实现对所有后代元素的取回,可以传递通配选择器 '*'。
  • find只在后代中遍历,不包括自己。
  • 选择器 context 是由 .find() 方法实现的;因此,$('.item-ii').find('li') 等价于 $('li', '.item-ii')(找到类名为item-ii的标签下的li标签)。

(3).find()和.children()方法是相似的

  • .children只查找第一级的子节点
  • .find查找范围包括子节点的所有后代节点

jQuery遍历之parent()方法

(1)jQuery是一个合集对象,如果想快速查找合集里面的每一个元素的父元素(这里可以理解为就是父亲-儿子的关系),此时可以用parent()方法,因为是父元素,这个方法只会向上查找一级

<div class="div">
    <ul class="son">
        <li class="grandson">1</li>
    </ul>
</div>
查找ul的父元素div, $(ul).parent(),就是这样简单的表达

jQuery遍历之parents()方法

(1)jQuery是一个合集对象,如果想快速查找合集里面的每一个元素的所有祖辈元素,此时可以用parents()方法,其实也类似find与children的区别,parent只会查找一级,parents则会往上一直查到查找到祖先节点

<div class="div">
    <ul class="son">
        <li class="grandson">1</li>
    </ul>
</div>
在li节点上找到祖 辈元素div, 这里可以用$("li").parents()方法

(2)注意事项:

  • .parents()和.parent()方法是相似的,但后者只是进行了一个单级的DOM树查找
  • $( "html" ).parent()方法返回一个包含document的集合,而$( "html" ).parents()返回一个空集合。

jQuery遍历之closest()方法

(1)closest()方法接受一个匹配元素的选择器字符串,从元素本身开始,在DOM 树上逐级向上级元素匹配,并返回最先匹配的祖先元素。例如:在div元素中,往上查找所有的li元素,可以这样表达
$("div").closet("li')
(2)注意事项:在使用的时候需要特别注意下,粗看.parents()和.closest()是有点相似的,都是往上遍历祖辈元素,但是两者还是有区别的,否则就没有存在的意义了

  • 起始位置不同:.closest开始于当前元素 .parents开始于父元素
  • 遍历的目标不同:.closest要找到指定的目标,.parents遍历到文档根元素,closest向上查找,直到找到一个匹配的就停止查找,parents一直查找到根元素,并将匹配的元素加入集合
  • 结果不同:.closest返回的是包含零个或一个元素的jquery对象,parents返回的是包含零个或一个或多个元素的jquery对象

jQuery遍历之next()方法

(1)jQuery是一个合集对象,如果想快速查找指定元素集合中每一个元素紧邻的后面同辈元素的元素集合,此时可以用next()方法

$("button:first").click(function() {
   $('.item-2').next().css('border','2px solid red')
})

jQuery遍历之prev()方法

(1)jQuery是一个合集对象,如果想快速查找指定元素集合中每一个元素紧邻的前面同辈元素的元素集合,此时可以用prev()方法
$("button:first").click(function() {
$('.item-2').prev().css('border','2px solid red')
})

jQuery遍历之siblings()

(1)jQuery是一个合集对象,如果想快速查找指定元素集合中每一个元素的同辈元素,此时可以用siblings()方法

//找到class=item-2的所有兄弟节点然后给每个li加上红色的边
$("button:first").click(function() {
   $('.item-2').siblings().css('border', '2px solid red')
})

$("button:last").click(function() {
    //找到class=item-2的所有兄弟节点
    //然后筛选出最后一个,加上蓝色的边
   $('.item-2').siblings(':last').css('border', '2px solid blue')
})

jQuery遍历之add()方法

(1)jQuery是一个合集对象,通过$()方法找到指定的元素合集后可以进行一系列的操作。$()之后就意味着这个合集对象已经是确定的,如果后期需要再往这个合集中添加一新的元素要如何处理?jQuery为此提供add方法,用来创建一个新的jQuery对象 ,元素添加到匹配的元素集合中
(2).add()的参数可以几乎接受任何的$(),包括一个jQuery选择器表达式,DOM元素,或HTML片段引用。
(3)操作方式:

<ul>
    <li>list item 1</li>
    <li>list item 3</li>
</ul>
<p>新的p元素</p>
  • 传递选择器
    $('li').add('p')
  • 传递dom元素
    $('li').add(document.getElementsByTagName('p')[0])
  • 动态创建P标签加入到合集
    $('li').add('<p>新的p元素</p>').appendTo(目标位置)

jQuery遍历之each()

(1).each() 方法就是一个for循环的迭代器,它会迭代jQuery对象合集中的每一个DOM元素。每次回调函数执行时,会传递当前循环次数作为参数(从0开始计数

$("button:first").click(function () {
  //遍历所有的li
  //修改每个li内的字体颜色
  $("li").each(function (index, element) {
    $(this).css('color', 'red')
  })
})

$("button:last").click(function () {
  //遍历所有的li
  //修改偶数li内的字体颜色
  $("li").each(function (index, element) {
    if (index % 2) {
      $(this).css('color', 'blue')
    }
  })
})

备注:index 索引;element是对应的li节点 li;this 指向的是li。

(2)三个重点

  • each是一个for循环的包装迭代器
  • each通过回调的方式处理,并且会有2个固定的实参,索引与元素
  • each回调方法中的this指向当前迭代的dom元素

jQuery事件篇

jQuery鼠标事件之click与dbclick事件

(1)click方法用于监听用户单击操作:

<div id="test">点击触发<div>
$("ele").click(function(){
    alert('触发指定事件')
})
$("#test").click(function(){
     $("ele").click()  //手动指定触发事件 
});

(2)dbclick方法用于监听用户双击操作,dblclick()的用法和click()的用法是类似的。
(3)注意:在同一元素上同时绑定 click 和 dblclick 事件是不可取的。各个浏览器事件触发的顺序是不同的,一些浏览器在dblclick之前接受两个 click 事件 ,而一些浏览器只接受一个 click 事件。用户往往可通过不同的操作系统和浏览器配置双击灵敏度

jQuery鼠标事件之mousedown与mouseup事件

(1)用户交互操作中,最简单直接的操作就是点击操作,因此jQuery提供了一个mousedown的快捷方法可以监听用户鼠标按下的操作,与其对应的还有一个方法mouseup快捷方法可以监听用户鼠标弹起的操作
(2)mousedown事件触发需要以下几点:

  • mousedown强调是按下触发
  • 如果在一个元素按住了鼠标不放,并且拖动鼠标离开这个元素,并释放鼠标键,这仍然是算作mousedown事件
  • 任何鼠标按钮被按下时都能触发mousedown事件
  • 用event 对象的which区别按键,敲击鼠标左键which的值是1,敲击鼠标中键which的值是2,敲击鼠标右键which的值是3

(3)mouseup事件触发需要以下几点:

  • mouseup强调是松手触发,与mousedown是相反的
  • mouseup与mousedown组合起来就是click事件
  • 如果用户在一个元素上按下鼠标按键,并且拖动鼠标离开这个元素,然后释放鼠标键,这仍然是算作mouseup事件
  • 任何鼠标按钮松手时都能触发mouseup事件
  • 用event 对象的which区别按键,敲击鼠标左键which的值是1,敲击鼠标中键which的值是2,敲击鼠标右键which的值是3

jQuery鼠标事件之mousemove事件

(1)用交互操作中,经常需要知道用户是否有移动的操作。基于移动的机制可以做出拖动、拖拽一系列的效果出来。针对移动事件,jQuery提供了一个mousemove的快捷方法可以监听用户移动的的操作
(2)mousemove事件触发需要以下几点:

  • mousemove事件是当鼠标指针移动时触发的,即使是一个像素
  • 如果处理器做任何重大的处理,或者如果该事件存在多个处理函数,这可能造成浏览器的严重的性能问题

jQuery鼠标事件之mouseover与mouseout事件

(1)jQuery当中同样提供了这样的事件来监听用户的移入移出操作,mouseover()与mouseout()事件,两者用法类似,下面一mouseover为例:
var n = 0;
//绑定一个mouseover事件
$(".aaron1 p:first").mouseover(function(e) {
$(".aaron1 a").html('进入元素内部,mouseover事件触发次数:' + (++n))
})

jQuery鼠标事件之mouseenter与mouseleave事件

(1)用交互操作中,经常需要知道用户操作鼠标是否有移到元素内部或是元素外部,因此jQuery提供了一个mouseenter和mouseleave的快捷方法可以监听用户移动到内部的操作
(2)mouseenter事件和mouseover的区别,关键点就是冒泡的方式处理问题。mouseover事件会层层向上冒泡,而mouseenter事件只会在绑定它的元素上被调用,而不会在后代节点上被触发。

jQuery鼠标事件之hover事件

(1)在元素上移进移出切换其换色,一般通过2个事件配合就可以达到,这里用mouseenter与mouseleave,这样可以避免冒泡问题

$(ele).mouseenter(function(){
     $(this).css("background", '#bbffaa');
 })
$(ele).mouseleave(function(){
    $(this).css("background", 'red');
})

(2)这样目的是达到了,代码稍微有点多,对于这样的简单逻辑jQuery直接提供了一个hover方法,可以便捷处理,只需要在hover方法中传递2个回调函数就可以了,不需要显示的绑定2个事件

// hover()方法是同时绑定 mouseenter和 mouseleave事件。
// 我们可以用它来简单地应用在 鼠标在元素上行为
$("p").hover(
   function() {
       $(this).css("background", 'red');
      },
     function() {
         $(this).css("background", '#bbffaa');
     }
);

jQuery鼠标事件之focusin事件

(1)当一个元素,或者其内部任何一个元素获得焦点的时候,例如:input元素,用户在点击聚焦的时候,如果开发者需要捕获这个动作的时候,jQuery提供了一个focusin事件

//input聚焦
//给input元素增加一个边框
  $("input:first").focusin(function() {
       $(this).css('border','2px solid red')
})

//不同函数传递数据
function fn(e) {
   $(this).val(e.data)
}
function a() {
   $("input:last").focusin('慕课网', fn)
}
a();

jQuery鼠标事件之focusout事件

(1)当一个元素,或者其内部任何一个元素失去焦点的时候,比如input元素,用户在点击失去焦的时候,如果开发者需要捕获这个动作,jQuery提供了一个focusout事件

//input失去焦点
//给input元素增加一个边框
  $("input:first").focusout(function() {
       $(this).css('border','2px solid red')
})

//不同函数传递数据
function fn(e) {
   $(this).val(e.data)
}
function a() {
   $("input:last").focusout('慕课网', fn)
}
a();

jQuery表单事件之blur与focus事件

(1)同样用于处理表单焦点的事件还有blur与focus事件,它们之间的本质区别:是否支持冒泡处理。

$(".aaron").focus(function() {
    $(this).css('border', '2px solid red')
})
$(".aaron1").focusin(function() {
   $(this).find('input').val('冒泡捕获了focusin事件')
})

$(".aaron3").blur(function() {
   $(this).css('border', '2px solid red')
})
$(".aaron4").focusout(function() {
   $(this).find('input').val('冒泡捕获了focusout事件')
})

jQuery表单事件之change事件

(1)input元素,textarea和select元素的值都是可以发生改变的,开发者可以通过change事件去监听这些改变的动作

//监听input值的改变
$('.target1').change(function (e) {
  $("#result").html(e.target.value)
});

//监听select:
$(".target2").change(function (e) {
  $("#result").html(e.target.value)
})

//监听textarea:
$(".target3").change(function (e) {
  $("#result").html(e.target.value)
})

jQuery表单事件之select事件

(1)当 textarea 或文本类型的 input 元素中的文本被选择时,会发生 select 事件。
(2)这个函数会调用执行绑定到select事件的所有函数,包括浏览器的默认行为。可以通过在某个绑定的函数中返回false来防止触发浏览器的默认行为。
(3)select事件只能用于input元素与textarea元素

//监听input元素中value的选中
//触发元素的select事件
$("input").select(function (e) {
  alert(e.target.value)
})
$("#bt1").click(function () {
  $("input").select();
})

//监听textarea元素中value的选中
$('textarea').select(function (e) {
  alert(e.target.value);
});

jQuery表单事件之submit事件

(1)提交表单是一个最常见的业务需求,比如用户注册,一些信息的输入都是需要表单的提交。同样的有时候开发者需要在表单提交的时候过滤一些的数据、做一些必要的操作(例如:验证表单输入的正确性,如果错误就阻止提交,从新输入)此时可以通过submit事件,监听下提交表单的这个动作。

//回车键或者点击提交表单
$('#target1').submit(function (e) {
  alert('捕获提交表达动作,不阻止页面跳转')
});

(2)注意:form元素是有默认提交表单的行为,如果通过submit处理的话,需要禁止浏览器的这个默认行为,传统的方式是调用事件对象 e.preventDefault() 来处理, jQuery中可以直接在函数中最后结尾return false即可。

//回车键或者点击提交表单,禁止浏览器默认跳转:
$('#target2').submit(function () {
  alert('捕获提交表达动作,阻止页面跳转')
  return false;
});

jQuery键盘事件之keydown()与keyup()事件

(1)鼠标有mousedown,mouseup之类的事件,这是根据人的手势动作分解的2个触发行为。相对应的键盘也有这类事件,将用户行为分解成2个动作,键盘按下与松手,针对这样的2种动作,jQuery分别提供了对应keydown与keyup方法来监听

//监听键盘按键
//获取输入的值
$('.target1').keydown(function (e) {
  $("em:first").text(e.target.value)
});

//监听键盘按键
//获取输入的值
$('.target2').keyup(function (e) {
  $("em:last").text(e.target.value)
});

(2)注意:

  • keydown是在键盘按下就会触发
  • keyup是在键盘松手就会触发
  • 理论上它可以绑定到任何元素,但keydown/keyup事件只是发送到具有焦点的元素上,不同的浏览器中,可获得焦点的元素略有不同,但是表单元素总是能获取焦点,所以对于此事件类型表单元素是最合适的。

jQuery键盘事件之keypress()事件

(1)在input元素上绑定keydown事件会发现一个问题:每次获取的内容都是之前输入的,当前输入的获取不到
(2)keydown事件触发在文字还没敲进文本框,这时如果在keydown事件中输出文本框中的文本,得到的是触发键盘事件前的文本,而keyup事件触发时整个键盘事件的操作已经完成,获得的是触发键盘事件后的文本
(3)keypress事件与keydown和keyup的主要区别:

  • 只能捕获单个字符,不能捕获组合键
  • 无法响应系统功能键(如delete,backspace)
  • 不区分小键盘和主键盘的数字字符

(4)总结:
KeyPress主要用来接收字母、数字等ANSI字符,而 KeyDown 和 KeyUP 事件过程可以处理任何不被 KeyPress 识别的击键。诸如:功能键(F1-F12)、编辑键、定位键以及任何这些键和键盘换档键的组合等。

on()的多事件绑定

(1)之前学的鼠标事件,表单事件与键盘事件都有个特点,就是直接给元素绑定一个处理函数,所有这类事件都是属于快捷处理。翻开源码其实可以看到,所有的快捷事件在底层的处理都是通过一个"on"方法来实现的。jQuery on()方法是官方推荐的绑定事件的一个方法。
(2)最常见的给元素绑定一个点击事件,对比一下快捷方式与on方式的不同,最大的不同点就是on是可以自定义事件名。

$("#elem").click(function(){})  //快捷方式
$("#elem").on('click',function(){}) //on方式

(3)多个事件绑定同一个函数
$("#elem").on("mouseover mouseout",function(){ });
(4)多个事件绑定不同函数

$("#elem").on({
    mouseover:function(){},  
    mouseout:function(){}
});

on()的高级用法

(1)针对自己处理机制中,不仅有on方法,还有根据on演变出来的live方法(1.7后去掉了),delegate方法等等。这些方法的底层实现部分 还是on方法,这是利用了on的另一个事件机制委托的机制衍变而来的

<h2>on事件委托</h2>
<div class="left">
  <div class="aaron">
    <a>点击这里</a>
  </div>
</div>
<script type="text/javascript">
  //给body绑定一个click事件
  //没有直接a元素绑定点击事件
  //通过委托机制,点击a元素的时候,事件触发
  $('body').on('click', 'a', function (e) {
    alert(e.target.textContent)
  })
</script>

卸载事件off()方法

(1)根据on绑定事件的一些特性,off方法也可以通过相应的传递组合的事件名,名字空间,选择器或处理函数来移除绑定在元素上指定的事件处理函数。当有多个过滤参数时,只有与这些参数完全匹配的事件处理函数才会被移除。

var n = 0;
//绑定事件
$(".aaron:first").on('mousedown mouseup', function (e) {
  $(this).text('触发类型:' + (e.type) + ",次数" + ++n)
    ++n;
})
//删除事件
$("button:first").click(function () {
  $(".aaron:first").off('mousedown')
})

var n = 0;
//绑定事件
$(".aaron:last").on('mousedown mouseup', function (e) {
  $(this).text('触发类型:' + (e.type) + ",次数" + ++n)
    ++n;
})
//删除事件
$("button:last").click(function () {
  $(".aaron:last").off()
})

jQuery事件对象的作用

(1)在不同浏览器之间事件对象的获取, 以及事件对象的属性都有差异。jQuery根据 W3C 标准规范了事件对象,所以在jQuery事件回调方法中获取到的事件对象是经过兼容后处理过的一个标准的跨浏览器对象。

<h3>事件委托,通过事件对象区别触发元素</h3>
<div class="left">
  <div class="aaron">
    <ul>
      <li>点击:触发一</li>
      <li>点击:触发二</li>
      <li>点击:触发三</li>
      <li>点击:触发四</li>
    </ul>
  </div>
</div>

<script type="text/javascript">
  //多事件绑定一
  $("ul").on('click', function (e) {
    alert('触发的元素是内容是: ' + e.target.textContent)
  })
</script>

(2)事件对象是用来记录一些事件发生时的相关信息的对象。事件对象只有事件发生时才会产生,并且只能是事件处理函数内部访问,在所有事件处理函数运行结束后,事件对象就被销毁。
(3)event.target代表当前触发事件的元素,可以通过当前元素对象的一系列属性来判断是不是我们想要的元素

jQuery事件对象的属性和方法

(1)event.type:获取事件的类型

$("a").click(function(event) {
  alert(event.type); // "click"事件
});

(2)event.pageX 和 event.pageY:获取鼠标当前相对于页面的坐标
(3)event.preventDefault() 方法:阻止默认行为
(4)event.stopPropagation() 方法:阻止事件冒泡
(5)event.which:获取在鼠标单击时,单击的是鼠标的哪个键
(6)event.currentTarget : 在事件冒泡过程中的当前DOM元素,冒泡前的当前触发事件的DOM对象, 等同于this。
(7)this和event.target的区别:

  • js中事件是会冒泡的,所以this是可以变化的,但event.target不会变化,它永远是直接接受事件的目标DOM元素
  • this和event.target都是dom对象,如果要使用jquey中的方法可以将他们转换为jquery对象,比如this和$(this)的使用、event.target和$(event.target)的使用。

jQuery自定义事件之trigger事件

(1)众所周知类似于mousedown、click、keydown等等这类型的事件都是浏览器提供的,通俗叫原生事件,这类型的事件是需要有交互行为才能被触发。
(2).trigger事件简单来讲就是:根据绑定到匹配元素的给定的事件类型执行所有的处理程序和行为

<h2>自定义事件trigger</h2>
<div class="left">
  <div><span></span><span>0</span>点击次数</div>
  <button>直接点击</button>
  <button>通过自定义点击</button>
</div>
<script type="text/javascript">
  //点击更新次数
  $("button:first").click(function (event, bottonName) {
    bottonName = bottonName || 'first';
    update($("span:first"), $("span:last"), bottonName);
  });

  //通过自定义事件调用,更新次数
  $("button:last").click(function () {
    $("button:first").trigger('click', 'last');
  });

  function update(first, last, bottonName) {
    first.text(bottonName);
    var n = parseInt(last.text(), 10);
    last.text(n + 1);
  }
</script>

(2)更多详情

jQuery自定义事件之triggerHandler事件

(1)trigger事件还有一个特性:会在DOM树上冒泡,所以如果要阻止冒泡就需要在事件处理程序中返回false或调用事件对象中的.stopPropagation() 方法可以使事件停止冒泡
(2)trigger事件是具有触发原生与自定义能力的,但是存在一个不可避免的问题: 事件对象event无法完美的实现,毕竟一个是浏览器给的,一个是自己模拟的。尽管 .trigger() 模拟事件对象,但是它并没有完美的复制自然发生的事件,若要触发通过 jQuery 绑定的事件处理函数,而不触发原生的事件,使用.triggerHandler() 来代替
(3)triggerHandler与trigger的用法是一样的,重点看不同之处:

  • triggerHandler不会触发浏览器的默认行为,.triggerHandler( "submit" )将不会调用表单上的.submit()
  • .trigger() 会影响所有与 jQuery 对象相匹配的元素,而 .triggerHandler() 仅影响第一个匹配到的元素
  • 使用 .triggerHandler() 触发的事件,并不会在 DOM 树中向上冒泡。 如果它们不是由目标元素直接触发的,那么它就不会进行任何处理
  • 与普通的方法返回 jQuery 对象(这样就能够使用链式用法)相反,.triggerHandler() 返回最后一个处理的事件的返回值。如果没有触发任何事件,会返回 undefined

jQuery动画篇

jQuery中隐藏元素的hide方法

(1)让页面上的元素不可见,一般可以通过设置css的display为none属性。但是通过css直接修改是静态的布局,如果在代码执行的时候,一般是通过js控制元素的style属性,这里jQuery提供了一个快捷的方法.hide()来达到这个效果

<h4>测试一</h4>
<div id="a1">hide操作</div>
<button>直接hide</button>
<script type="text/javascript">
  //点击buttom1 直接隐藏
  $("button:first").click(function () {
    $("#a1").hide()
  });
</script>

(2)当提供hide方法一个参数时,.hide()就会成为一个动画方法。.hide()方法将会匹配元素的宽度,高度,以及不透明度,同时进行动画操作

<h4>测试一</h4>
<div id="a2">hide动画操作</div>
<button>hide带动画</button>
<script type="text/javascript">
  //点击buttom2 执行动画隐藏
  $("button:last").click(function () {
    $("#a2").hide({
      duration: 3000,
      complete: function () {
        alert('执行3000ms动画完毕')
      }
    })
  });
</script>

(3)注意:jQuery在做hide操作的时候,是会保存本身的元素的原始属性值,再之后通过对应的方法还原的时候还是初始值。比如一个元素的display属性值为inline,那么隐藏再显示时,这个元素将再次显示inline。一旦透明度 达到0,display样式属性将被设置为none,这个元素将不再在页面中影响布局

jQuery中显示元素的show方法

(1)css中有display:none属性,同时也有display:block,所以jQuery同样提供了与hide相反的show方法,方法的使用几乎与hide是一致的,hide是让元素显示到隐藏,show则是相反,让元素从隐藏到显示

//点击button
//执行3秒隐藏
//执行3秒显示
$("button").click(function () {
  $("#a1").hide(3000).show(3000)
});

(2)注意:

  • show与hide方法是修改的display属性,通过是visibility属性布局需要通过css方法单独设置
  • 如果使用!important在你的样式中,比如display: none !important,如果你希望.show()方法正常工作,必须使用.css('display', 'block !important')重写样式
  • 如果让show与hide成为一个动画,那么默认执行动画会改变元素的高度,高度,透明度

jQuery中显示与隐藏切换toggle方法

(1)show与hide是一对互斥的方法。需要对元素进行显示隐藏的互斥切换,通常情况是需要先判断元素的display状态,然后调用其对应的处理方法。比如显示的元素,那么就要调用hide,反之亦然。 对于这样的操作行为,jQuery提供了一个便捷方法toggle用于切换显示或隐藏匹配元素

<h2>通过toggle切换显示与隐藏</h2>
<div class="left">显示到隐藏</div>
<div class="right">隐藏到显示</div>
<button>直接show-hide动画</button>
<button>直接hide-show动画</button>
<script type="text/javascript">
  $("button:first").click(function () {
    $(".left").toggle(3000)
  });
</script>

<script type="text/javascript">
  $("button:last").click(function () {
    $(".right").toggle(3000)
  });
</script>

jQuery中下拉动画slideDown

(1).slideDown():用滑动动画显示一个匹配元素,将给匹配元素的高度的动画,这会导致页面的下面部分滑下去,弥补了显示的方式

<h2>slideDown</h2>
<div class="left">
  <h4>测试一</h4>
  <div id="a1">hide-show</div>
  <button>点击slideDown显示动画</button>
</div>
<script type="text/javascript">
  //点击button
  //执行3秒隐藏
  //执行3秒显示
  $("button:first").click(function () {
    $("#a1").slideDown(3000)
  });
</script>
<div class="right">
  <h4>测试二</h4>
  <div id="a2">hide-show</div>
  <button>点击slideDown执行回调</button>
</div>
<script type="text/javascript">
  //点击button
  //执行3秒隐藏
  //执行3秒显示
  $("button:last").click(function () {
    $("#a2").slideDown(3000, function () {
      alert('动画执行结束')
    })
  });
</script>

(2)注意事项:

  • 下拉动画是从无到有,所以一开始元素是需要先隐藏起来的,可以设置display:none
  • 如果提供回调函数参数,callback会在动画完成的时候调用。将不同的动画串联在一起按顺序排列执行是非常有用的。这个回调函数不设置任何参数,但是 this会设成将要执行动画的那个DOM元素,如果多个元素一起做动画效果,那么要非常注意,回调函数会在每一个元素执行完动画后都执行一次,而不是这组 动画整体才执行一次

jQuery中上卷动画slideUp

(1)对于显示的元素,在将其隐藏的过程中,可以对其进行一些变化的动画效果。之前学过了hide方法,hide方法在显示的过程中也可以有动画,但 是.hide()方法将为匹配元素的宽度,高度,以及不透明度,同时进行动画操作。这里将要学习一个新的显示方法slideUp方法
(2)因为动画是异步的,所以要在动画之后执行某些操作就必须要写到回调函数里面,这里要特别注意

<h2>slideUp</h2>
<div class="left">
  <h4>测试一</h4>
  <div id="a1"></div>
  <button>点击slideUp隐藏动画</button>
</div>
<script type="text/javascript">
  //点击button
  //执行3秒隐藏
  //执行3秒显示
  $("button:first").click(function () {
    $("#a1").slideUp(3000)
  });
</script>
<div class="right">
  <h4>测试二</h4>
  <div id="a2"></div>
  <button>点击slideUp执行回调</button>
</div>
<script type="text/javascript">
  //点击button
  //执行3秒隐藏
  //执行3秒显示
  $("button:last").click(function () {
    $("#a2").slideUp(3000, function () {
      alert('动画执行结束')
    })
  });
</script>

jQuery中上卷下拉切换slideToggle

(1)slideDown与slideUp是一对相反的方法。需要对元素进行上下拉卷效果的切换,jQuery提供了一个便捷方法slideToggle用滑动动画显示或隐藏一个匹配元素

<h2>slideToggle</h2>
<div class="left">
  <div id="a1"></div>
  <button>点击slideToggle上下卷滚切换</button>
</div>
<script type="text/javascript">
  $("button").click(function () {
    $("#a1").slideToggle(3000)
  });
</script>

jQuery中淡出动画fadeOut

(1)fadeOut()函数用于隐藏所有匹配的元素,并带有淡出的过渡动画效果

<h2>fadeOut</h2>
<p>测试文字淡入效果</p>
<p>慕课网,专注分享</p>
淡出的隐藏效果:
<select id="animation">
  <option value="1">fadeOut( )</option>
  <option value="2">fadeOut( "slow" )</option>
  <option value="3">fadeOut( 3000 )</option>
  <option value="4">fadeOut( 1000, complete )</option>
  <option value="5">fadeOut( 1000, "linear" )</option>
  <option value="6">fadeOut( options )</option>
</select>
<input id="btnFadeOut" type="button" value="点击淡出隐藏" />
<input id="btnShow" type="button" value="显示" />

<script type="text/javascript">
  //【显示】按钮
  $("#btnShow").click(function () {
    $("p").show();
  });

  //【隐藏】按钮
  $("#btnFadeOut").click(function () {
    var v = $("#animation").val();
    if (v == "1") {
      $("p").fadeOut();
    } else if (v == "2") {
      $("p").fadeOut("slow");
    } else if (v == "3") {
      $("p").fadeOut(3000);
    } else if (v == "4") {
      $("p").fadeOut(2000, function () {
        alert("隐藏完毕!");
      });
    } else if (v == "5") {
      $("p").fadeOut(1000, "linear");
    } else if (v == "6") {
      $("p").fadeOut({
        duration: 1000
      });
    }
  });
</script>

jQuery中淡入动画fadeIn

(1)fadeOut是淡出效果,相反的还有淡入效果fadeIn,方法使用上两者都是一致的,只是结果相反
(2)注意

  • 淡入的动画原理:操作元素的不透明度从0%逐渐增加到100%
  • 如果元素本身是可见的,不对其作任何改变。如果元素是隐藏的,则使其可见

jQuery中淡入淡出切换fadeToggle

(1)fadeToggle()函数用于切换所有匹配的元素,并带有淡入/淡出的过渡动画效果。之前也学过toggle、slideToggle 也是类似的处理方式
(2)fadeToggle切换fadeOut与fadeIn效果,所谓"切换",即如果元素当前是可见的,则将其隐藏(淡出);如果元素当前是隐藏的,则使其显示(淡入)。

jQuery中淡入效果fadeTo

(1)淡入淡出fadeIn与fadeOut都是修改元素样式的opacity属性,但是他们都有个共同的特点,变化的区间要么是0,要么是1
(2)jQuery提供了fadeTo方法,可以设置透明度的数值

<h2>fadeTo</h2>
<p>测试文字透明度效果</p>
<p>慕课网,专注分享</p>
透明度的设置效果:
<select id="animation">
  <option value="1">fadeTo( "slow" ,0.5 )</option>
  <option value="2">fadeTo( 1000 ,0.2 )</option>
  <option value="3">fadeTo( 1000 ,0.9 ,complete)</option>
</select>
<input id="btnFadeSwitch" type="button" value="点击切换显示/隐藏">
<script type="text/javascript">
  //【切换显示/隐藏】按钮
  $("#btnFadeSwitch").click(function () {
    var v = $("#animation").val();
    if (v == "1") {
      $("p").fadeTo("slow", 0.5);
    } else if (v == "2") {
      $("p").fadeTo(1000, 0.2);
    } else if (v == "3") {
      $("p").fadeTo(1000, 0.9, function () {
        alert('完成')
      });
    }
  });
</script>

jQuery中toggle与slideToggle以及fadeToggle的比较

(1)toggle、sildeToggle以及fadeToggle的区别

  • toggle:切换显示与隐藏效果
  • sildeToggle:切换上下拉卷滚效果
  • fadeToggle:切换淡入淡出效果

(2)toggle与slideToggle细节区别

  • toggle:动态效果为从右至左。横向动作,toggle通过display来判断切换所有匹配元素的可见性
  • slideToggle:动态效果从下至上。竖向动作,slideToggle 通过高度变化来切换所有匹配元素的可见性

(3)fadeToggle方法

  • fadeToggle() 方法在 fadeIn() 和 fadeOut() 方法之间切换。
  • 元素是淡出显示的,fadeToggle() 会使用淡入效果显示它们。
  • 元素是淡入显示的,fadeToggle() 会使用淡出效果显示它们。
  • 注释:隐藏的元素不会被完全显示(不再影响页面的布局)

jQuery中动画animate(上)

(1)有些复杂的动画通过之前学到的几个动画函数是不能够实现,这时候就需要强大的animate方法了,操作一个元素执行3秒的淡入动画,对比一下2组动画设置的区别

$(elem).fadeOut(3000)  
$(elem).animate({   
    opacity:0
},3000)

(2).animate()方法允许我们在任意的数值的CSS属性上创建动画。

<h2>animate(上)</h2>
<p>慕课网,专注分享</p>
<div id="aaron">内部动画</div>
点击观察动画效果:
<select id="animation">
  <option value="1">动画1</option>
  <option value="2">动画2</option>
  <option value="3">动画3</option>
  <option value="4">动画4</option>
</select>
<input id="exec" type="button" value="执行动画">

<script type="text/javascript">
  $("#exec").click(function () {
    var v = $("#animation").val();
    var $aaron = $("#aaron");
    if (v == "1") {
      // 数值的单位默认是px
      $aaron.animate({
        width: 300,
        height: 300
      });
    } else if (v == "2") {
      // 在现有高度的基础上增加100px
      $aaron.animate({
        width: "+=100px",
        height: "+=100px"
      });
    } else if (v == "3") {
      $aaron.animate({
        fontSize: "5em"
      }, 2000, function () {
        alert("动画 fontSize执行完毕!");
      });
    } else if (v == "4") {
      //通过toggle参数切换高度
      $aaron.animate({
        width: "toggle"
      });
    }
  });
</script>

jQuery中动画animate(下)

(1)animate在执行动画中,如果需要观察动画的一些执行情况,或者在动画进行中的某一时刻进行一些其他处理,我们可以通过animate提供的第二种设置语法,传递一个对象参数,可以拿到动画执行状态一些通知
(2)语法:.animate( properties, options ),options参数

  • duration - 设置动画执行的时间
  • easing - 规定要使用的 easing 函数,过渡使用哪种缓动函数
  • step:规定每个动画的每一步完成之后要执行的函数
  • progress:每一次动画调用的时候会执行这个回调,就是一个进度的概念
  • complete:动画完成回调

jQuery中停止动画stop

(1)动画在执行过程中是允许被暂停的,当一个元素调用.stop()方法,当前正在运行的动画(如果有的话)立即停止
(2)语法:

.stop( [clearQueue ], [ jumpToEnd ] )
.stop( [queue ], [ clearQueue ] ,[ jumpToEnd ] )
  • .stop(); 停止当前动画,点击在暂停处继续开始
  • .stop(true); 如果同一元素调用多个动画方法,尚未被执行的动画被放置在元素的效果队列中。这些动画不会开始,直到第一个完成。当调用.stop()的时候,队列中的下一个动画立即开始。如果clearQueue参数提供true值,那么在队列中的动画其余被删除并永远不会运行
  • .stop(true,true); 当前动画将停止,但该元素上的 CSS 属性会被立刻修改成动画的目标值
<h2>stop</h2>
<p>慕课网,专注分享</p>
<div id="aaron">内部动画</div>
<input id="exec" type="button" value="执行动画"><br /><br /> 点击观察动画效果:
<select id="animation">
  <option value="1">stop()</option>
  <option value="2">stop(true)</option>
  <option value="3">stop(true,true)</option>
</select>
<a></a>
<input id="stop" type="button" value="停止动画">
<script type="text/javascript">
  //点击执行动画
  $("#exec").click(function () {
    $("#aaron").animate({
      height: 300
    }, 5000)
    $("#aaron").animate({
      width: 300
    }, 5000)
    $("#aaron").animate({
      opacity: 0.6
    }, 2000)
  })
	
	//点击停止动画
  $("#stop").click(function () {
    var v = $("#animation").val();
    var $aaron = $("#aaron");
    if (v == "1") {
      //当前当前动画
      $aaron.stop()
    } else if (v == "2") {
      //停止所以队列
      $aaron.stop(true)
    } else if (v == "3") {
      //停止动画,直接跳到当前动画的结束
      $aaron.stop(true, true)
    }
  });
</script>

jQuery中each方法的应用

(1)jQuery提供了一个通用的jQuery.each方法,用来处理对象和数组的遍历
(2)each就是for循环方法的一个包装,内部就是通过for遍历数组与对象,通过回调函数返回内部迭代的一些参数,第一个参数是当前迭代成员在对象或数组中的索引值(从0开始计数),第二个参数是当前迭代成员(与this的引用相同

<h2>each方法</h2>
<p>慕课网,专注分享</p>
<div id="aaron"></div>
点击观察结果:
<select id="animation">
  <option value="1">each数组</option>
  <option value="2">each对象</option>
</select>
<input id="exec" type="button" value="执行动画">
<script type="text/javascript">
  $("#exec").click(function () {
    var v = $("#animation").val();
    var $aaron = $("#aaron");
    $aaron.empty();
    if (v == "1") {
      // 遍历数组元素
      $.each(['Aaron', '慕课网'], function (i, item) {
        $aaron.append("索引=" + i + "; 元素=" + item);
      });
    } else if (v == "2") {
      // 遍历对象属性
      $.each({
        name: "张三",
        age: 18
      }, function (property, value) {
        $aaron.append("属性名=" + property + "; 属性值=" + value);
      });
    }
  });
</script>

jQuery中查找数组中的索引inArray

(1)jQuery封装了inArray()函数判断元素是否存在数组中。注意了:在ECMAScript5已经有数据的indexOf方法支持了,但是jQuery保持了版本向下兼容,所以封装了一个inArray方法
(2)jQuery.inArray()函数用于在数组中搜索指定的值,并返回其索引值。如果数组中不存在该值,则返回 -1。
$.inArray(5,[1,2,3,4,5,6,7]) //返回对应的索引:4

jQuery中去空格神器trim方法

(1)jQuery.trim()函数用于去除字符串两端的空白字符,这个函数很简单,没有多余的参数用法

$("#exec1").click(function () {
  alert("值的长度:" + $("#results1").val().length)
});

$("#exec2").click(function () {
  alert("值的长度:" + $.trim($("#results2").val()).length)
});

(2)注意:

  • 移除字符串开始和结尾处的所有换行符,空格(包括连续的空格)和制表符(tab)
  • 如果这些空白字符在字符串中间时,它们将被保留,不会被移除

jQuery中DOM元素的获取get方法

(1)jQuery是一个合集对象,如果需要单独操作合集中的的某一个元素,可以通过.get()方法获取到,比如第二个a元素的查找: $(a).get(1)
(2)注意

  • get方法是获取的dom对象,也就是通过document.getElementById获取的对象
  • get方法是从0开始索引

(3)get方法还可以从后往前索引,传递一个负索引值,注意的负值的索引起始值是-1,同样是找到第二个元素,可以传递$(a).get(-2)

jQuery中DOM元素的获取index方法

(1)get方法是通过已知的索引在合集中找到对应的元素。如果反过来,已知元素如何在合集中找到对应的索引呢?.index()方法,从匹配的元素中搜索给定元素的索引值,从0开始计数。
(2)要点

  • 如果不传递任何参数给 .index() 方法,则返回值就是jQuery对象中第一个元素相对于它同辈元素的位置
  • 如果在一组元素上调用 .index() ,并且参数是一个DOM元素或jQuery对象, .index() 返回值就是传入的元素相对于原先集合的位置
  • 如果参数是一个选择器, .index() 返回值就是原先元素相对于选择器匹配元素的位置。如果找不到匹配的元素,则 .index() 返回 -1
<h2>index方法</h2>
<ul>
  <a></a>
  <a></a>
  <li id="test1">1</li>
  <li id="test2">2</li>
  <li id="test3">3</li>
</ul>
<ul>
  <li id="test4">4</li>
  <li id="test5">5</li>
  <li id="test6">6</li>
</ul>
<select id="animation">
  <option value="1">index无参数</option>
  <option value="2">index传递dom</option>
  <option value="3">index传递jQuery对象</option>
</select>
<input id="exec" type="button" value="点击执行">
索引结果:
<span></span>
<script type="text/javascript">
  $("#exec").click(function () {
    var v = $("#animation").val();
    var $span = $("span");
    $span.empty();
    if (v == "1") {
      //找到第一个li的同辈节点中的索引位置
      $span.text($("li").index())
    } else if (v == "2") {
      //通过传递dom查找
      $span.text($("li").index(document.getElementById("test5")))
    } else if (v == "3") {
      //通过传递jQuery对象查找
      $span.text($("li").index($("#test6")))
    }
  });
</script>

jQuery的Ajax应用与常用插件

使用load()方法异步请求数据

(1)使用load()方法通过Ajax请求加载服务器中的数据,并把返回的数据放置到指定的元素中,它的调用格式为:load(url,[data],[callback])。参数url为加载服务器地址,可选项data参数为请求时发送的数据,callback参数为数据请求成功后,执行的回调函数。

$(function () {
  $("#btnShow").bind("click", function () {
    var $this = $(this);
    $("ul")
      .html("<img src='PSD/img/demo1.png' alt=''/>")
      .load("http://www.imooc.com/data/fruit_part.html", function () {
        $this.attr("disabled", "true");
      });
  })
});

使用getJSON()方法异步加载JSON格式数据

(1)使用getJSON()方法可以通过Ajax异步请求的方式,获取服务器中的数据,并对获取的数据进行解析,显示在页面中,它的调用格式为:jQuery.getJSON(url,[data],[callback])或$.getJSON(url,[data],[callback]),其中,url参数为请求加载json格式文件的服务器地址,可选项data参数为请求时发送的数据,callback参数为数据请求成功后,执行的回调函数。

<div id="divtest">
  <div class="title">
    <span class="fl">我最喜欢的一项运动</span>
    <span class="fr">
      <input id="btnShow" type="button" value="加载" />
    </span>
  </div>
  <ul></ul>
</div>

<script type="text/javascript">
  $(function () {
    $("#btnShow").bind("click", function () {
      var $this = $(this);
      $.getJSON("demo.json", function (data) {
        $this.attr("disabled", "true");
        $.each(data, function (index, sport) {
          if (index == 3)
            $("ul").append("<li>" + sport["name"] + "</li>");
        });

      });
    });
  });
</script>

(2)demo.json文件源码

[
  {
    "name": "足球"
  },
  {
    "name": "散步"
  },
  {
    "name": "篮球"
  },
  {
    "name": "乒乓球"
  },
  {
    "name": "骑自行车"
  }
]

使用getScript()方法异步加载并执行js文件

(1)使用getScript()方法异步请求并执行服务器中的JavaScript格式的文件,它的调用格式如下所示:jQuery.getScript(url,[callback])或$.getScript(url,[callback])。参数url为服务器请求地址,可选项callback参数为请求成功后执行的回调函数。

$(function () {
  $("#btnShow").bind("click", function () {
    var $this = $(this);
    $.getScript('http://www.imooc.com/data/sport_f.js', function () {
      $this.attr("disabled", "true");
    });
  })
});

使用get()方法以GET方式从服务器获取数据

(1)使用get()方法时,采用GET方式向服务器请求数据,并通过方法中回调函数的参数返回请求的数据,它的调用格式如下:$.get(url,[callback])

$(function () {
  $("#btnShow").bind("click", function () {
    var $this = $(this);
    $.get("demo.json", function (data) {
      $this.attr("disabled", "true");
      $("ul").append("<li>我的名字叫:" + data.name + "</li>");
      $("ul").append("<li>男朋友对我说:" + data.say + "</li>");
    }, "json");
  })
});

使用post()方法以POST方式从服务器发送数据

(1)与get()方法相比,post()方法多用于以POST方式向服务器发送数据,服务器接收到数据之后,进行处理,并将处理结果返回页面,调用格式如下:$.post(url,[data],[callback])。参数url为服务器请求地址,可选项data为向服务器请求时发送的数据,可选项callback参数为请求成功后执行的回调函数。

$(function () {
  $("#btnCheck").bind("click", function () {
    $.post("demo.php", {
        num: $("#txtNumber").val()
      },
      function (data) {
        $("ul").append("<li>你输入的<b>  " +
          $("#txtNumber").val() + " </b>是<b> " +
          data + " </b></li>");
      });
  })
});

使用serialize()方法序列化表单元素值

(1)使用serialize()方法可以将表单中有name属性的元素值进行序列化,生成标准URL编码文本字符串,直接可用于ajax请求,它的调用格式如下:$(selector).serialize()。其中selector参数是一个或多个表单中的元素或表单元素本身

<div id="divtest">
  <div class="title">
    <span class="fl">我的个人资料</span>
    <span class="fr">
      <input id="btnAction" type="button" value="序列化" />
    </span>
  </div>
  <form action="">
    <ul>
      <li>姓名:<input name="Text1" type="text" size="12" /></li>
      <li>
        <select name="Select1">
          <option value="0">男</option>
          <option value="1">女</option>
        </select>
      </li>
      <li><input name="Checkbox1" type="checkbox" />资料是否可见 </li>
      <li id="litest"></li>
    </ul>
  </form>
</div>

<script type="text/javascript">
  $(function () {
    $("#btnAction").bind("click", function () {
      $("#litest").html($("form").serialize());
    })
  })
</script>

使用ajax()方法加载服务器数据

(1)使用ajax()方法是最底层、功能最强大的请求服务器数据的方法,它不仅可以获取服务器返回的数据,还能向服务器发送请求并传递数值,它的调用格式如下:jQuery.ajax([settings])或$.ajax([settings])
其中参数settings为发送ajax请求时的配置对象,在该对象中,url表示服务器请求的路径,data为请求时传递的数据,dataType为服务器返回的数据类型,success为请求成功的执行的回调函数,type为发送数据请求的方式,默认为get。

<div id="divtest">
  <div class="title">
    <span class="fl">加载一段文字</span>
    <span class="fr">
			<input type="button" id="btnShow" value="加载" />
		</span>
  </div>
  <ul></ul>
</div>

<script>
  $(function () {
    $("#btnShow").bind("click", function () {
      var that = $(this);
      $.ajax({
        type: "get",
        url: "demo.txt",
        dataType: "text",
        success: function (data) {
          that.attr("disabled", "true");
          $("ul").append(data);
        }
      });
    })
  })
</script>

使用ajaxSetup()方法设置全局Ajax默认选项

(1)使用ajaxSetup()方法可以设置Ajax请求的一些全局性选项值,设置完成后,后面的Ajax请求将不需要再添加这些选项值,它的调用格式为:jQuery.ajaxSetup([options])或$.ajaxSetup([options])。可选项options参数为一个对象,通过该对象设置Ajax请求时的全局选项值。

<div id="divtest">
  <div class="title">
    <span class="fl">加载一段文字</span>
    <span class="fr">
			<input type="button" id="btnShow01" value="加载1" />
			<input type="button" id="btnShow02" value="加载2" />
		</span>
  </div>
  <ul></ul>
</div>

<script>
  $(function () {
    $.ajaxSetup({
      dataType: "text",
      success: function (data) {
        $("ul").empty().append(data);
      }
    });
    $("#btnShow01").bind("click", function () {
      $.ajax({
        type: "get",
        url: "demo.txt",
        async: true
      });
    });
    $("#btnShow02").bind("click", function () {
      $.ajax({
        type: "get",
        url: "demo1.txt",
        async: true
      });
    })
  })
</script>

使用ajaxStart()和ajaxStop()方法

(1)ajaxStart()和ajaxStop()方法是绑定Ajax事件。ajaxStart()方法用于在Ajax请求发出前触发函数,ajaxStop()方法用于在Ajax请求完成后触发函数。它们的调用格式为:$(selector).ajaxStart(function())和$(selector).ajaxStop(function())。其中,两个方法中括号都是绑定的函数,当发送Ajax请求前执行ajaxStart()方法绑定的函数,请求成功后,执行ajaxStop ()方法绑定的函数。

<div id="divtest">
  <div class="title">
    <span class="fl">加载一段文字</span>
    <span class="fr">
      <input id="btnShow" type="button" value="加载" />
    </span>
  </div>
  <ul>
    <li id="divload"></li>
  </ul>
</div>

<script type="text/javascript">
  $(function () {
    $('#divload').ajaxStart(function () {
      $(this).html("正在请求数据...");
    });
    $('#divload').ajaxStop(function () {
      $(this).html("数据请求完成!");
    });
    $("#btnShow").bind("click", function () {
      var $this = $(this);
      $.ajax({
        url: "demo.json",
        dataType: "json",
        success: function (data) {
          $this.attr("disabled", "true");
          $("ul").append("<li>我的名字叫:" + data.name + "</li>");
          $("ul").append("<li>男朋友对我说:" + data.say + "</li>");
        }
      });
    })
  });
</script>

Hello,移动web基础知识整理

前言

这篇文章是根据慕课网的课程——《hello,移动web》,虽然课程已经很久远了,但是细细读来还是能够get到很多的知识,下面进入正文。

移动开发中的像素(px)

首先,抛出一个问题:640x1136的图片,能不能在iPhone5上完全显示?毕竟iPhone5的分辨率是为640x1136的。空想无用,现在我们用下面一段代码进行测试一下

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Test</title>
    <style>
      #app {
        width: 640px;
        height: 1136px;
        background-color: green;
        color: #fff;
        font-size: 150px;
        text-align: center;
        font-family: "微软雅黑";
      }
    </style>
  </head>
  <body>
    <div id="app">
      hello Mobile
    </div>
  </body>
</html>

打开Chrome浏览器,模拟iPhone5手机进行查看,看到结果了吗?是不是很惊讶,盒子居然没有铺满全屏,而且在浏览器上显示的iPhone5尺寸是320px*568px,这到底是怎么回事?不用太惊讶,我们先捋一捋各种像素之间的关联马上就能明白。

物理像素与逻辑像素傻傻分不清楚?

首先,iPhone5的分辨率(640x1136)指的是物理像素,也可以说是设备无关像素,单位是dp或者pt,状态无法改变,厂家设置为多少就是多少;而我们开始使用的CSS样式中px代表的是逻辑像素,是浏览器使用的抽象单位,状态是可变的。这两种像素之间的关系就是由设备像素缩放比(dpr)连接起来的。下面来看一下它们之间的计算公式。

计算公式:1px = (dpr)² * dp

那么看到上面的公式,我们再来想一想,为什么iPhone5在浏览器显示的尺寸是320x568?那是因为在iPhone5中,dpr的值为2。可能大家还是有点懵,好了,下面我们再继续分析下。

在平面上:1px = (2)²xdp,那么在维度上就是:1px = 2xdp,这里我们需要搞清楚平面和维度 之间的关系。于是我们就能够得出0.5px = 1dp,则推理出320px = 640dp,568px = 1136dp。这样一步步推算下来,是不是非常简单?

关于DPR的种种

好了,默认上面的知识大家都理解了,下面我们来看看为什么iPhone5的DPR为2?

在手机上,有个叫PPI的东西,它代表的是手机设备屏幕每英寸的像素数量,即单位英寸内的像素密度。

每英寸的像素密度计算方式:PPI = 根号(1136²+640²),再除以4。为什么要除以4呢?因为iPhone5的屏幕尺寸为4英寸啊。然后根据这个公式就能够得出iPhone5的像素密度为:根号(1136²+640²)/4 = 326ppi

通过分析、推理可知,单位英寸内像素密度(PPI)越高,则物理像素数越高,于是图像就越清晰。

在图像越清晰的时候,可视度就越低,系统默认设置缩放比越大。比如,当你的电脑屏幕设备分辨率为1920x1200时,桌面的图标和文字会比较小(相对);当电脑屏幕设备分辨率为1280x720时,桌面的图标和文字会比较大(相对)。

谈到这个缩放比,界内有个标准图
image
Ldpi代表低像素密度;mdpi代表中像素密度;hdpi代表高像素密度;xhdpi代表超高像素密度。

最后一张图来总结上面的推算公式,其实非常好理解,只要你认认真真看两遍,亲自动手动脑整理一遍,一下子就明了。
image

Viewport相关知识

还是先抛出一个问题:一个PC的页面在移动设备上展示的效果是怎么样的?答案就是整个页面都铺满在手机上了(不过里面的文字和照片什么的全都变小了)!是不是非常的unimaginable?其实这就是在html文档中Viewport的神秘力量。

手机浏览器默认为我们做了两件事: 1、使页面渲染在一个980px(IOS)的布局viewport中;2、对页面进行缩放。

为什么手机浏览器在渲染一个页面时,要有Viewport?那是因为Viewport是手机浏览器虚拟出来的一个区域,目的是为了避免PC端的页面到了手机端可能会出现的布局错乱问题。

但是在真正的开发中,我们却不会去使用默认的980px的布局viewport,为什么不使用呢?原因如下:

  • 宽度不可控制,不同系统不同设备的默认值都可能不同;
  • 页面缩小版显示,交互不友好;
  • 链接不可点击;
  • 有缩放,缩放后又有滚动;
  • 最主要的是font-size为40px等于PC上12px同等物理大小,不规范

基于以上原因,我们可以通过meta标签去改变默认的viewport,欲知详情,往下翻即可。

Viewport中meta标签

设置Viewport中meta标签的默认语法为:
<meta name=”viewport” content=”name=value,name=value”>

name值一般有如下几种:

  • width:设置布局viewport的特定值(device-width)
  • initial-scale:设置页面的初始缩放
  • minimum-scale:最小缩放
  • maximum-scale:最大缩放
  • user-scalable:用户能否缩放

拿到页面默认的布局viewport值语法是:document.body.clientWidth

取得度量viewport值的语法是:window.innerWidth

那么问题来了,度量viewport到底是个啥玩意啊?其实我真的很晕,下面是我做的一些调查分析:
image
根据上面那副图我暂时分析出来的东东就是这样的:
document.body.clientHeight/Width的值默认取CSS样式中定义的元素高和宽,假如在meta标签中有对height和width定义,那么document.body.clientHeight/Width取得的值就是meta标签中定义的值,这就是一些布局viewport特性;

度量viewport我就很懵逼了,假如它也是和元素的宽高对应的话,那为啥window.innerHeight为622px而不是668px?!阿西八!

关于度量viewport这玩意还得和屏幕缩放比结合在一起探究探究,张鑫旭大大说的是:window.innerWidth/innerHeight指的是浏览器内部宽度/高度的大小 ,我们接着往后看。

继续探讨一下,如何将宽高为320x568的元素铺满整个iPhone5手机浏览器屏幕呢? 很简单,只要我们设置meta标签中的content=”width=320”即可,下面请看完整代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=320" />
    <title>Test</title>
    <style>
      body {
        margin: 0;
      }
      #app {
        width: 320px;
        height: 568px;
        background-color: green;
        color: #fff;
        text-align: center;
        font-family: "微软雅黑";
      }
    </style>
  </head>
  <body>
    <div id="app">
      hello Mobile
    </div>
  </body>
</html>

假如不出效果,那么记得多刷新下页面。

然而按照上面的写法,页面宽度根本无法适应所有手机浏览器屏幕,于是就有了一个非常神奇的属性:content=”width=device-width”。width=device-width这个属性值的含义就是布局viewport时刻等于我们的设备viewport。

接下来我们来看看屏幕缩放比这玩意,它的计算方式为:window.innerWidth/document.body.clientWidth

用文字表达就是:度量viewport/布局viewport = 屏幕的缩放比。

于是当我们控制缩放比的时候:
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0" />,就强制让度量viewport等于布局viewport了。

总结就是: 移动web最佳viewport设置为:布局viewport = 设备宽度 = 度量viewport

关于移动端开发,还有以下几个知识点需要好好学习

  • flexbox弹性盒子布局:这是我自己做的一些总结,看了还是不会的话过来打我啊;
  • rem单位:说到rem不得不先说一下em单位,em单位是根据父节点的font-size为相对单位;而rem是根据html的font-size为相对单位。因为rem的特性,从而使它成为了适配移动端页面的热门名词。只是不应该使用rem的是页面的字体大小,字体大小尽量为一个固定值为好;
  • vh和vw单位:vw 相对于视窗的宽度,视窗宽度是100vw;vh 相对于视窗的宽度,视窗宽度是100vh。更详细的内容请来张鑫旭大大的博客进行查阅。

移动端开发1px边框问题

这是retina屏幕下的问题,根本原因是因为:1px使用了2dp渲染。目前成熟的解决方案是使用缩放(scaleY(0.5)),下面贴核心代码

.folder li {
  position: relative;
  padding: 8px 0 8px 15px;
  color: #FFFFFF;
  cursor: pointer;
}
.folder li + li:before {
  position: absolute;
  top: -1px;
  left: 0;
  content: "";
  width: 100%;
  height: 1px;
  border-top: 1px solid #ddd;
  transform: scaleY(0.5);
}

移动端的单行文本溢出和多行文本溢出解决方式

不多说了,直接看代码

/*单行文本溢出*/
.inaline {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/*多行文本溢出*/
.intwoline {
  display: -webkit-box !important;
  overflow: hidden;
  text-overflow: ellipsis;
  word-break: break-all;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

移动端300ms的故事

移动web页面上的click事件响应都要慢上300ms。原因是在开始的时候移动设备访问的web页面都是PC上的页面,在默认的viewport(980px)的页面往往都是需要“双击”或“捏开”放大页面来看清页面。而正是为了确认用户是“双击”还是“单击”,safari浏览器需要个300ms的延迟来判断。而后来的iPhone也一直延用这样的设计,而借鉴成功iPhone的android也延用这样的设计。于是300ms就成为了一道规范。

如何解决300ms延迟的问题?
使用Tab事件代替click事件,其中的原理是:在touchstart、touchend时记录时间、手指的位置,在touchend时进行比较,如果手指位置为同一位置(或允许移动一个非常小的位移值)且时间间隔较短(一般认为是200ms),且过程中未曾触发过touchmove,即可认为触发了手持设备上的“click”,一般称它为“tap”

移动端Tap穿透的小bug

这个小bug是这样的,在手机端有个区域绑定了一个点击事件,事件上面有一个蒙层,当点击蒙层,蒙层消失的时候却触发下面那个区域的点击事件。

造成这个小bug的原因是因为移动端的300ms延迟特性,点击蒙层消失的那一瞬间,点击事件就被记录下来了,接着下层区域的点击事件被触发。

那么该如何解决这个问题呢?

  • 使用缓冲动画,过渡300ms的延迟;
  • 加入中间层dom元素,让中间层接受这个“穿透”事件,稍后这个中间层就隐藏。
  • “上下”都使用Tab事件,原理上解决tab穿透事件,但是不可避免原生标签的click事件。
  • 使用fastclick.js这个库

暂时就说到这里,课程后面的内容不做整理,因为自身对于移动开发的经验不足,看后面的视频也是迷迷糊糊的状态,应该和现在的大前端有一定的关系,毕竟如今的前端开发的趋势是基于数据驱动开发,而不是基于DOM开发。

JavaScript正则表达式

一、前言

该博文源自慕课网的《JavaScript正则表达式》课程。老师讲的生动有趣,由浅入深,是一门好课程。为了让自己更好记忆和继续深究正则表达式,因此写下该篇博文。下面推荐两个实用的在线网站:
正则表达式工具
代码及时响应在线网站

二、实例化正则对象

在JavaScript中,一共有两种方法实例化正则对象,下面请看具体的方法。

1、 字面量:var reg = /\bis\b/g;
【注意】
\b代表单词边界,g代表全文进行匹配。如果没有\b和g,会怎么样呢?请动手分别进行测试一下,下面给出测试模板,内容可自行更改。
测试:'he is a boy,This is a dog'.replace(reg,'IS');

2、 构造函数:var reg = new RegExp('\\bis\\b','g');
【注意】
在构造函数中,我们需要\来进行转义。
测试:'he is a boy,This is a dog'.replace(reg,'IS');

三、修饰符

在JavaScript正则表达式中,一共有三种修饰符,分别是一下三种。
1、 g:global 全文搜索,假如不添加的话,那么搜索到第一个匹配即停止;
测试:'He is a boy,she is here?'.replace(/\bis\b/g,'0');

2、 i:ignore case 忽略大小写,默认大小写敏感;
测试:'He is a boy,she IS here?'.replace(/\bis\b/gi,'0');

3、 m:multiple lines 多行搜索。
测试:'@123\n@234\n@345'.replace(/^@\d/gm,'Q');

四、元字符

JavaScript正则表达式中,元字符非常之多,下面我们一起来耐心看看。

1、 \s:匹配单个空格,等同于[\f\n\r\t\v];而\S则恰恰相反,它匹配的是非空格字符。

测试:/\s.+/.exec('This is a test String.');
【分析】
其中.含义是除了回车符和换行符之外的所有字符,+含义是出现一次或多次(至少出现一次),exec() 方法用于检索字符串中的正则表达式的匹配。假如有点晕,再来看看下面的栗子

var regs = /\s.+/
var str = "this is dog stil here"
// thishaha
 console.log(str.replace(regs,"haha"))

【应用场景】
匹配任意空或空白字符,如果你什么也没输入,或输入的只有空格、回车、换行等字符,则匹配成功。这样就可以验证用户是否正确输入内容了。
【用法】

var  reg=/^\s*$/;
if(reg.test(value)){
  alert('请输入有效值');
  return false;
}

【分析】
其中^含义是以什么为开始,*含义是出现零次或多次(任意次),$含义是以什么为结束,test() 方法用于检测一个字符串是否匹配某个模式.

2、 \w:表示单词字符,等同于字符集合[a-zA-Z0-9_];而\W表示非单词字符,等效于[^a-zA-Z0-9_]。

var reg = /\w+/;
var str='fengxiong';
alert(reg.exec(str));

【分析】
返回完整的fengxiong字符串,因为所有字符都是单词字符。

var reg = /\w+/;
var str='.className';
alert(reg.exec(str));

【分析】
结果显示匹配了字符串中的className,只有第一个“.”唯一的非单词字符没有匹配。

var reg = /\w+/;
var str='正则教程';
alert(reg.exec(str));

【分析】
试图用单词字符去匹配中文自然行不通了,返回 null。

var reg = /\W+/;
var str='正则教程';
alert(reg.exec(str));

【分析】
返回完整的字符串,因为,中文算作是非单词字符。

3、其他元字符
\f:匹配换页符;
\n:匹配换行符;
\r:匹配回车符;
\t:匹配制表符;
\v:匹配垂直制表符;
\d:匹配数字;
\D:匹配非数字;
\b:匹配单词边界;
\B:匹配非单词边界

来个测试题:将符合一个ab+数字+任意字符的字符串代替为B。
答案:'ab32432dab2,'.replace(/ab\d./g,'B');

五、重要元字符

1、^:
可以用来创建反向类/负向类,也就是不属于某类的内容,比如:
'a1b2c3d4'.replace(/[^abc]/g,'X');

又有以什么为开头的含义,比如:
'1 fafs'.replace(/^\d\s/g,'Q');

2、[ ]
可以用来构建一个简单的类,也就是几个字符归纳为集合,比如:
'a1b2c3d4'.replace(/[abc]/g,'X');

可以使用[a-z]来连接两个字符表示从a到z的任意字符,比如:
'a1b2d3x4z9'.replace(/[a-z]/g,'A');

在[]组成的类内部是可以连写的,比如:
'a1b2d3x4z9B7A3N4M8'.replace(/[a-zA-Z]/g,'J');

如何在[]把-给算上呢?其实只要把-加在后面即可,比如:
'2018-01-14'.replace(/[0-9-]/g,'A');

六、量词

?:出现零次或一次(最多出现一次);
+:出现一次或多次(至少出现一次);
*:出现零次或多次(任意次);
{n}:出现n次;
{n,m}:出现n到m次;
{n,}:至少出现n次。

八、分组

1、使用()可以达到分组的功能,使用量词作用于分组,比如:
'a1b2c3d4'.replace(/([a-z]\d){3}/g,'X');
'BoyGirl'.replace(/Boy|Girl/g,'X');
'BoyGirlBoyBorl'.replace(/Boy(Gi|Bo)rl/g,'X');

2、分组的反向引用,比如,将2018-01-14 转换为 01/14/2018,对比下面两段代码的结果:
'2018-01-14'.replace(/\d{4}-\d{2}-\d{2}/g,'$2/$3/$1');
'2018-01-14'.replace(/(\d{4})-(\d{2})-(\d{2})/g,'$2/$3/$1');

八、贪婪模式与非贪婪模式

1、贪婪模式:尽可能多的匹配,比如:
'12345678'.replace(/\d{3,6}/g,'X');

2、非贪婪模式:让正则表达式尽可能少的匹配,也就是说一旦成功匹配则不再继续尝试,比如:
'12345678'.replace(/\d{3,6}?/g,'X');

九、正则表达式训练营

1、用正则匹配手机号码

function tele(tel) {
  if (tel.search(/^1[34578]\d{9}$/g) > -1) {
    console.log("1");
  } else {
    console.log("0");
  }
}
tele("13456799014");

【分析】
寻找以13、14、15、17或18开头,以9个数字结尾的字符,找到了就返回1,失败就返回0。search()方法去匹配字符串,如果匹配成功,就返回匹配成功的位置,如果匹配失败就返回-1

还有一种更加简便的方法:

function tele(tel) {
  return /^1[34578]\d{9}$/g.test(tel);
}
console.log(tele("13456799014"));

【分析】
test()方法的返回值是布尔值,通过该值可以匹配字符串中是否存在于正则表达式相匹配的结果,如果有匹配内容,返回ture,如果没有匹配内容返回false。
【拓展】
match()方法去匹配字符串,如果匹配成功,就返回匹配成功的数组,如果匹配不成功,就返回null;
replace()方法去匹配字符串,匹配成功的字符去替换新的字符串。

2、判断字符串是否包含了数字

function contain(str) {
  var reg = /\d/g;
  return reg.test(str);
}
console.log(contain("fds3af"));

3、给定字符串str,检查其是否包含连续重复的字母,包含返回true,否则返回false。

function contain(str) {
   return /([a-zA-Z])\1/.test(str);
}
console.log(contain("fdsaaf"));

【分析】
"小括号包含的表达式所匹配到的字符串" 不仅是在匹配结束后才可以使用,在匹配过程中也可以使用。表达式后边的部分,可以引用前面 "括号内的子匹配已经匹配到的字符串"。引用方法是 "/" 加上一个数字。"/1" 引用第1对括号内匹配到的字符串,"/2" 引用第2对括号内匹配到的字符串……以此类推,如果一对括号内包含另一对括号,则外层的括号先排序号。换句话说,哪一对的左括号 "(" 在前,那这一对就先排序号。

4、判断是否以元音字母结尾。

function contain(str) {
  return /[a,e,i,o,u]$/i.test(str);
}
console.log(contain("fdsaaa"));

5、给定字符串str,检车其是否包含3个连续的数字

function contain(str) {
  return str.match(/\d{3}/g);
}
console.log(contain("1g556777"));

6、判断是否符合指定格式(正确格式示例:556-142-7489)

function contain(str) {
  return /^(\d{3}-){2}\d{4}$/g.test(str);
}
console.log(contain("235-894-5623"));

【解析】
以3个数字加“-”开头,并且重复2次,最后为4个数字。

7、待处理
写一个正则表达式,匹配 ""

let str = '<OPTION  value="待处理">待处理</OPTION>';
let regExp = /^<.*?>/g;
console.log(regExp.exec(str)[0]); 

【分析】
以<开头,匹配除了回车符合换行符之外的所有字符,出现任意次,出现零次或一次,这就匹配到了所有的字符。exec()方法返回一个匹配项的数组,而match()方法返回所有匹配项组成的数组。

8、如何获取一个字符串中的数字字符,并按数组形式输出,如:
dgfhfgh254bhku289fgdhdy675gfh输出[254,289,675]

let str = 'dgfhfgh254bhku289fgdhdy675gfh';
let regExp = /\d+/g;
console.log(str.match(regExp));

【分析】
+含义是出现一次或多次(至少出现一次)。

9、敏感词过滤

let str = '我草**哈哈背景天胡景涛哪肉涯剪短发欲望';
let regExp = /草|肉|欲|胡景涛/g;
let result = str.replace(regExp,"*");
console.log(result);

以上的是缩减版,下面来个完整版的:

let str = '我草**哈哈背景天胡景涛哪肉涯剪短发欲望';
let regExp = /草|肉|欲|胡景涛/g;
let result = str.replace(regExp, function (match) {
  let len = match.length;
  let str;
  switch (len) {
    case 1:
      str = '*';
      break;
    case 2:
      str = "**";
      break;
    case 3:
      str = "***";
      break;
    default:
      str = '****';
  }
  return str;
});
console.log(result); //我***哈哈背景天***哪*涯剪短发*望

10、让2013-6-7 变成 2013.6.7

let str = '2013-6-7';
let regExp = /-/g;
console.log(str.replace(regExp, '.')); //2013-6-7

11、给定这样一个连字符串,写一个function转换为驼峰命名法形式的字符串 getElementById

var s1 = "get-element-by-id";
function camelCased(str) {
  let regExp = /-(\w)/g;
  str.replace(regExp, function(match, p) {
      return p.toUpperCase();
   })
}
camelCased(s1);

【分析】
\w代表的是单词字符

12、判断字符串中是否包含数字

let str1 = 'abc9efh';
let str2 = 'abcefg';
let regExp = /\d/;
console.log(regExp.test(str1)); // true
console.log(regExp.test(str2)); // false

13、判断连续重复字母

let str1 = 'abc3d4e5';
let str2 = 'aab2c3';
let regExp = /([a-zA-Z])\1/;
console.log(regExp.test(str1));//false
console.log(regExp.test(str2));//true

14、给定字符串 str,检查其是否包含 3 个连续的数字

  • 如果包含,返回最新出现的 3 个数字的字符串
  • 如果不包含,返回 false
let str1 = 'abc123efg';
function captureThreeNumbers(str) {
    let res;
    if (res = str.match(/\d{3}/)) {
        return res[0];
    } else {
        return false;
    }
}
console.log(captureThreeNumbers(str1)); //123

15、给定字符串 str,检查其是否符合美元书写格式

  • 以 $ 开始
  • 整数部分,从个位起,满 3 个数字用 , 分隔
  • 如果为小数,则小数部分长度为 2
  • 正确的格式如:$1,023,032.03 或者 $2.03,错误的格式如:$3,432,12.12 或者 $34,344.3
let regExp = /^\$\d{1,3}(,\d{3})*(\.\d{2})?$/;
console.log(regExp.test('$1.23')); //true
console.log(regExp.test('$111.23')); //true 
console.log(regExp.test('$1111.23')); //false
console.log(regExp.test('$1,123.23')); //true

【分析】
以$开头,匹配数字符1到3个;匹配逗号(,)再匹配3个数字符,该规则出现零次或多次(即任意次);匹配句号(.),再匹配2个数字符,该规则出现零次或一次,并在结尾处。

16、对人口数字的格式化处理,三位数字用一个','(逗号)隔开

function numberWithCommas(x) {
    //对右侧人口数字的格式化处理,三位数字用一个','(逗号)隔开
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
console.log(numberWithCommas(12345678))//12,345,678

【分析】
非单词边界,匹配右边有三个数字符的数(正向前瞻),匹配一次或者多次;匹配负向前瞻。

17、将单词is替换为IS

let str = 'English poetry is one of their great heritages';
console.log(str.replace(/\bis\b/,'IS'));
// English poetry IS one of their great heritages

18、实现数字转千分位

var reg = /\d{1,3}(?=(\d{3})+$)/g;
var str = "1234556789";
console.log(str.replace(reg,"$&,"));

【分析】
数字千分位的特点是:第一个逗号后面数字的个数是3的倍数,正则:/(\d{3})+$/;第一个逗号前最多可以有1至3个数字,正则:/\d{1,3}/。加起来就是/\d{1,3}(\d{3})+$/,?=含义是零宽度正预测先行断言,具体用法自行百度^_^
【其他】

var reg = /\d{1,3}(?=(\d{3})+$)/g;
var str = 1234556789;
console.log(str.toString().replace(reg,"$&,"));

十、参考文章

由于正则表达式真的是难以灵活运用和讲清楚,因为它实在是太强大了,只有不断的训练自己,多看并多练才有可能完全掌握它,下面提供一些参考文章。

面试系列:关于正则表达式
正则表达式面试题
一些正则表达式的随记
正则表达式总结1
从面试到正则
正则表达式总结2

JavaScript实现二叉树算法

前言

这篇文章的内容几乎全部来源于慕课网教程——《JavaScript实现二叉树》,教程内容值得一看,虽然最终的游戏代码那块讲述的并不完整,然而让人基本的认知到什么是二叉树这是讲的挺不错的,下面就来看看我的一些摘抄。

为什么是JavaScript和数据结构?

JavaScript自从诞生以来,经过多年的发展,目前已经成为几乎是最流行的语言,俗称为“互联网编程语言”。他的发展越来越快,并且将触角延伸到了各个领域,几乎有一统江湖之势。

从客户端而言,特使是web应用开发上,它是当之无愧的首选,结合各种强大的开发框架,运用JavaScript可以开发出功能相当强大的web桌面应用,例如像Gmail这种完全能媲美于原生桌面程序的web应用,就是通过JavaScript开发的。

从服务器而言,原本被C++,java等老牌语言占据着不可动摇的地位,当以JavaScript为开发语言的Node.js平台诞生后,老牌语言在服务器领域的地位在不断消亡,Node.js就像野火一样,在服务器开发领域熊熊燃烧。

最后,在移动开发领域,由于React Native,或lonic等移动开发框架的出现,使得运用JavaScript就能开发出同时运行在IOS和Android平台上的移动App。由此可见,学习和使用JavaScript这门编程语言是性价比最高的。

为什么要学习数据结构?

程序=算法+数据结构,计算机程序设计的本质是将业务逻辑转换为数理逻辑,通过逻辑推理以及数理运算解决客观世界存在的困难,而算法和数据结构就是数理逻辑的推演模式和展现方法。如果把编程语言比作文字,那么算法和数据结构就相当于语法,没有合理的语法,文字就无法准确的传达意义。

数据结构就相当于:我塞牙了,那么就要用到牙签这“数据结构”,当然你用指甲也行,只不过“性能”没那么好;我要拧螺母,肯定用扳手这个“数据结构”,当然你用钳子也行,只不过也没那么好用。学习数据结构,就是为了了解以后在IT行业里搬砖需要用到什么工具,这些工具有什么利弊,应用于什么场景。以后用的过程中,你会发现这些基础的“工具”也存在着一些缺陷,你不满足于此工具,此时,你就开始自己在这些数据结构的基础上加以改造,这就叫做自定义数据结构。而且,你以后还会造出很多其他应用于实际场景的数据结构。。你用这些数据结构去造轮子,不知不觉,你成了又一个轮子哥。

单步调试

概念: 单步调试是指程序开发中,为了找到程序的bug,通常采用的一种调试手段,一步一步跟踪程序执行的流程,根据变量的值,找到错误的原因。

Chrome浏览器单步调试步骤: 打开开发工具 —>点击sources—>点击要调试的文件—>点击某一条要调试的代码(左侧行数)—>刷新页面—>点击页面上的下一步来查看显示的结果是否和预期的一样。

排序二叉树

排序二叉树最大的功能就是能够快速有效的对很多数据进行排序。它的特点是左孩子的数值要小于根节点,右孩子的数值要大于根节点,详情请看下图。

什么是二叉树?

二叉树是一种具有层级特性的数据结构,一棵树包含多个节点(下图中的每一个圆圈),节点自身含有一个属性,就是它所代表的数值(圆圈中的数值)。节点与节点间有对应关系,一种叫做父子关系,例如图中,节点8引出一个箭头指向节点3,于是我们说,节点8是节点3的父亲,节点3是节点8的儿子;另外一种叫做兄弟关系,比如节点3和节点10,因为他们都有同一个父节点。
image

二叉树创建代码实现

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <script>
      function BinaryTree() {
        //创建一个节点
        var Node = function (key) {
          this.key = key;
          this.left = null;
          this.right = null;
        };
        //创建一个根节点
        var root = null;
        var insertNode = function (node, newNode) {
          if (newNode.key < node.key) {
            if (node.left === null) {
              node.left = newNode;
            } else {
              insertNode(node.left, newNode);
            }
          } else {
            if (node.right === null) {
              node.right = newNode;
            } else {
              insertNode(node.right, newNode);
            }
          }
        }
        //创建一个函数用来插入节点
        this.insert = function (key) {
          var newNode = new Node(key);
          if (root === null) {
            root = newNode;
          } else {
            insertNode(root, newNode);
          }
        };
      }
      var nodes = [8, 3, 10, 1, 6, 14, 4, 7, 13];
      var binaryTree = new BinaryTree();
      nodes.forEach(function (key) {
        binaryTree.insert(key);
      });
    </script>
  </body>
</html>

二叉树中序遍历

遍历规则:左中右。比如上图中序遍历为:1、3、4、6、7、8、10、13、14

二叉树中序遍历核心代码

//中序遍历的方法
var inOrderTraverseNode = function (node, callback) {
  if (node !== null) {
    inOrderTraverseNode(node.left, callback);
    callback(node.key);
    inOrderTraverseNode(node.right, callback);
  }
}
//中序遍历的接口
this.inOrderTraverse = function (callback) {
  inOrderTraverseNode(root, callback);
}

完整代码

二叉树前序遍历算法原理

使用前序遍历去赋值一颗二叉树效率是最高的。前序遍历规则是:中左右。比如上图前序遍历为:8、3、1、6、4、7、10、14、13

前序遍历核心代码

//前序遍历的方法
var preOrderTraverseNode = function (node, callback) {
  if (node !== null) {
    callback(node.key);
    preOrderTraverseNode(node.left, callback);
    preOrderTraverseNode(node.right, callback);
  }
}
//前序遍历的接口
this.preOrderTraverse = function (callback) {
  preOrderTraverseNode(root, callback);
}

完整代码

二叉树后序遍历原理

后序遍历可以应用到操作系统的文件系统的遍历之中,遍历规则为:左右中。比如上图后序遍历为:1、4、7、6、3、13、14、10、8

二叉树后序遍历核心代码

//后序遍历的方法
var postOrderTraverseNode = function (node, callback) {
  if (node !== null) {
    postOrderTraverseNode(node.left, callback);
    postOrderTraverseNode(node.right, callback);
    callback(node.key);
  }
}
//后序遍历的接口
this.postOrderTraverse = function (callback) {
  postOrderTraverseNode(root, callback);
}

完整代码

二叉树排序的相关应用

拓展

打起精神来,文章到这还没结束呢,这里不是讲解小游戏代码的实现原理,而是继续二叉树另外一个重要的概念“红黑树”!

偷下懒,下面给大家推荐一则十分良心有趣的漫画——什么是红黑树?

Promise初入门径

前言

这篇博文是根据慕课网教程整理而来,内容几乎都会是讲师的原话,外带一些自己的理解。

Promise是什么

这个英语单词翻译成中文意思就是:许诺;允诺;有可能。因此从字面上就可以知道它代表了即将要发生的事情,从而联想到了JavaScript中异步程序。

按照它的实际用途来看主要有以下几点

  • 用于异步计算
  • 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
  • 可以在对象之间传递和操作Promise,帮助我们处理队列

Promise产生的背景

根源是为了优化表单提交的用户体验,而开发了JavaScript这款包含大量异步操作的脚本语言。在提交表单中异步程序的表现是怎么样的呢?就是当你注册会员的时候,填写了昵称这玩意,然后再填写密码的时候,同时服务器里会检测这个昵称是否已经被注册从而做出一些回应,而不用等你全部信息填写好点击提交才告诉你昵称已经存在。

借由异步的这一个特点,可以想到:异步操作能够避免界面冻结!异步的本质用大白话说就是:将耗时很长的A交付的工作交给系统之后,就去继续做B交付的工作。等到系统完成前面的工作之后,再通过回调或者事件,继续做A交付的剩下的工作。

从观察者的角度看起来,AB工作的完成顺序,和交付它们的时间顺序无关,所以叫“异步”。

咳咳,说重点,以下才是Promise诞生的原因

  • 解决因为异步操作所带来的回调地狱,从而导致维护性差,下面请看回调代码
a(function (resultsFromA) {
  b(resultsFromA, function (resultsFromB) {
    c(resultsFromB, function (resultsFromC) {
      d(resultsFromC, function (resultsFromD) {
        e(resultsFromD, function (resultsFromE) {
          f(resultsFromE, function (resultsFromF) {
            console.log(resultsFromF);
          })
        })
      })
    })
  })
});
  • 总结就是曾经的异步操作依赖的回调函数中存在着“嵌套层次深,难以维护”、“无法正常使用return和throw”、“无法正常检索堆栈信息”和“多个回调之间难以建立联系”这四个主要问题需要被解决,于是Promise横空出世。
  • 最后一点:到底啥是回调函数啊??!

Promise的概念和优点

【优点】

  • Promise是一个代理对象,它和原先要进行的操作并无关系
  • Promise通过引入一个回调,避免了更多的回调

【状态】

  • pending:待定,称为初始状态
  • fulfilled:实现,称为操作成功状态
  • rejected:被否决,称为操作失败状态
  • 当Promise状态发生改变的时候,就会触发.then()里的响应函数来处理后续步骤
  • Promise状态已经改变,就不会再变

Promise的基本语法

new Promise(
    /* 执行器 executor */
    function (resolve, reject) {
      // 一段耗时很长的异步操作
      resolve(); // 数据处理完成
      reject(); // 数据处理出错
    }
 ).then(function A() {
    // 成功,下一步
  }, function B() {
    // 失败,做相应处理
  });

异步操作的常见方法

首先看课程里提供的方法

// 事件侦听与响应
document.getElementById('start').addEventListener('click', start, false);
function start() {
  // 响应事件,进行相应的操作
}

// jQuery 用 `.on()` 也是事件侦听
$('#start').on('click', start);

// 回调,比较常见的有ajax
$.ajax('http://baidu.com', {
  success: function (res) {
    // 这里就是回调函数了
  }
});

// 或者在页面加载完毕后回调
$(function () {
  // 这里也是回调函数
});

以上是课程稍微提到的方法,下面请看阮一峰老师的进一步说明,面试的时候可以使劲说啦,传送门在此。

Promise一个简单的例子

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
      console.log(123);
    }, 2000);
  })
  .then(name => {
    console.log(name + ' world');
  });

以上代码和课程稍微有些不同,目的是和定时器做一些对比,以此发现一点什么。

console.log('here we go');
setTimeout(() => {
  callback("hello");
  console.log(123);
}, 2000)

function callback(name) {
  console.log(name + ' world');
}

通过以上两段代码的运行结果比较,可以浅显的得出:resolve()状态引发的then()是异步的,更多的我暂时就不知道啦。

Promise两步执行的范例

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  })
  .then(value => {
    console.log(value);
    return new Promise(resolve => {
      setTimeout(() => {
        resolve('world');
      }, 2000);
    });
  })
  .then(value => {
    console.log(value + ' world');
  });

这个范例主要是简单的演示了Promise如何解决回调地狱这个让人头大的问题。

对已经完成的Promise执行then()

console.log('start');
let promise = new Promise(resolve => {
  setTimeout(() => {
    console.log('the promise fulfilled');
    resolve('hello, world');
  }, 1000);
});

setTimeout(() => {
  promise.then(value => {
    console.log(value);
  });
}, 3000);

讲师的原话:这段代码展示了Promise作为队列这个重要的特性,就是说我们在任何一个地方生成了一个Promise对象,都可以把它当做成一个变量传递到其他地方执行。不管Promise前面的状态到底有没有完成,队列都会按照固定的顺序去执行。

then()不返回Promise

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  })
  .then(value => {
    console.log(value);
    console.log('everyone');
    (function () {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('Mr.Laurence');
          resolve('Merry Xmas');
        }, 2000);
      });
    }());
    return false;
  })
  .then(value => {
    console.log(value + ' world');
  });

我对以上代码的理解是这样的:最后一个then()方法里的value值代表的是上一个then()里的返回值,当没有return的时候,默认返回值为undefined。而resolve()里的数据为什么没被调用呢?因为上一个then()方法里return的是false而不是Promise实例。

要想调用resolve()里的数据,只要这么写就可以了

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  })
  .then(value => {
    console.log(value);
    console.log('everyone');
    (function () {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('Mr.Laurence');
          resolve('Merry Xmas');
        }, 2000);
      });
    }()).then(value => {
      console.log(value + ' world');
    });
  })

then()解析

  • then()接受两个函数作为参数,分别代表fulfilled和rejected
  • then()返回一个新的Promise实例,所以它可以链式调用
  • 当前面的Promise状态改变时,then()根据其最终状态,选择特定的状态响应函数执行
  • 状态响应函数可以返回新的Promise或其他值
  • 如果返回新的Promise,那么下一级then()会在新的Promise状态改变之后执行
  • 如果返回其他任何值,则会立刻执行下一级then()

then()的嵌套

then()里面有then()的情况:因为then()返回的还是Promise实例,故会等里面的then()执行完,再执行外面的,因此对于我们来说,此时最好将其展开,会更好的进行阅读。以下是then嵌套的代码

console.log('start');
new Promise(resolve => {
    console.log('Step 1');
    setTimeout(() => {
      resolve(100);
    }, 1000);
  })
  .then(value => {
    return new Promise(resolve => {
        console.log('Step 1-1');
        setTimeout(() => {
          resolve(110);
        }, 1000);
      })
      .then(value => {
        console.log('Step 1-2');
        return value;
      })
      .then(value => {
        console.log('Step 1-3');
        return value;
      });
  })
  .then(value => {
    console.log(value);
    console.log('Step 2');
  });

解套后的代码为:

console.log('start');
new Promise(resolve => {
    console.log('Step 1');
    setTimeout(() => {
      resolve(100);
    }, 1000);
  })
  .then(value => {
    return new Promise(resolve => {
      console.log('Step 1-1');
      setTimeout(() => {
        resolve(110);
      }, 1000);
    })
  })
  .then(value => {
    console.log('Step 1-2');
    return value;
  })
  .then(value => {
    console.log('Step 1-3');
    return value;
  })
  .then(value => {
    console.log(value);
    console.log('Step 2');
  });

两段代码的执行结果一致(话说此时你们清楚的知道结果是啥吗)。

Promise小测试

看以下代码进行分析四种Promise的区别是什么?是什么原因导致了不同的执行流程?

// 问题一
doSomething()
  .then(function () {
    return doSomethingElse();
  })
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(resultDoSomethingELlse)

// 问题二
doSomething()
  .then(function () {
    doSomethingElse();
  })
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(undefined)
//注意:doSomethingElse(undefined)和finalHandler(undefined)同时执行

// 问题三
doSomething()
  .then(doSomethingElse())
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(resultOfDoSomething)
//注意:doSomethingElse(undefined)和doSomething()同时执行


// 问题四
doSomething()
  .then(doSomethingElse)
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(resultOfDoSomething) ==> finalHandler(resultOfDoSomethingElse)

Promise错误处理

因为在这一块,讲师貌似犯了些小错误,很多人反应很强烈,至于这错误到底是不是错误我也不太懂,但是不能把有异议的内容也写进来吧,于是我在网上找了篇自己能理解的Promise错误处理,贴上来给大家看看。

谈到Promise错误处理,就要把reject拿出来晾一晾了。reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调(严格来说这不算是错误处理吧。。。),看下面的代码。

function getNumber() {
  var p = new Promise(function(resolve, reject) {
    //做一些异步操作
    setTimeout(function() {
      var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
      if(num <= 5) {
        resolve(num);
      } else {
        reject('数字太大了');
      }
    }, 2000);
  });
  return p;
}
getNumber().then(function(data) {
  console.log('resolved');
  console.log(data);
}, function(reason) {
  console.log('rejected');
  console.log(reason);
});

getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为失败的原因。

运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到“成功”和“失败”的两种结果。

另一种处理错误和异常的方法:catch。 其实它和上面then的第二个参数一样,用来指定reject的回调,用法是这样的:

function getNumber() {
  var p = new Promise(function(resolve, reject) {
    //做一些异步操作
    setTimeout(function() {
      var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
      if(num <= 5) {
        resolve(num);
      } else {
        reject('数字太大了');
      }
    }, 2000);
  });
  return p;
}
getNumber().then(function(data) {
  console.log('resolved');
  console.log(data);
}).catch(function(reason) {
  console.log('rejected');
  console.log(reason);
});

效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中,请看下面的代码,然后分别代入自行测试一下

// 测试代码1
getNumber().then(function(data) {
  console.log(name());
  console.log('resolved');
  console.log(data);
}, function(reason, data) {
  console.log('rejected');
  console.log(reason);
});
// 测试代码2
getNumber().then(function(data) {
  console.log(name());
  console.log('resolved');
  console.log(data);
}).catch(function(reason) {
  console.log('rejected');
  console.log(reason);
});

在resolve的回调中,我们console.log(name());而name()这个函数是没有被定义的。如果我们不用Promise中的 catch,代码运行到这里就直接在控制台报错了,不往下运行,但是使用catch就不同了。

也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

Promise.all()解析

Promise.all()具有批量执行的特点,用于将多个Promise实例,包装成一个新的Promise实例,返回的就是普通Promise。

它接受一个数组作为参数,数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态的改变。

当所有子Promise都完成,那么返回新的Promise才认为是完成了,返回值是全部值的数组;有任何一个失败,则新的Promise就认为失败了,返回值是第一个失败的子Promise的结果。下面看代码:

console.log('here we go');
Promise.all([1, 2, 3]).then(all => {
  console.log('1:', all);
  return Promise.all([function() {
    console.log('ooxx');
  }, 'xxoo', false]);
}).then(all => {
  console.log('2:', all);
  let p1 = new Promise(resolve => {
    setTimeout(() => {
      resolve('I\'m P1');
    }, 1500);
  });
  let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('I\'m P2');
    }, 1000);
  });
  let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('I\'m P3');
    }, 3000);
  });
  return Promise.all([p1, p2, p3]);
}).then(all => {
  console.log('all', all);
}).catch(err => {
  console.log('Catch:', err);
});

真是让人头大的代码,建议各位还是先来看看这篇吧——大白话讲解Promise

Promise实现队列

有时候我们不希望所有动作一起发生,而是按照一定顺序,逐个进行,用代码解释就是如下这样的:

let promise = doSomething();
promise = promise.then(doSomethingElse);
promise = promise.then(doSomethingElse2);
promise = promise.then(doSomethingElse3);
.........

实现队列方式一:使用forEach()

function queue(things) {
  let promise = Promise.resolve();
  things.forEach(thing => {
   promise.then(() => {
      return new Promise(resolve => {
        doThing(thing, () => {
          resolve();
        });
      });
    });
  });
  return promise;
}
queue(['lots', 'of', 'things', ....]);

【注意】
常见错误:没有把then()产生的新Promise实例赋给promise,没有生成队列。

实现队列方式二:使用reduce()

function queue(things) {
  return things.reduce((promise, thing) => {
    return promise.then(() => {
      return new Promise(resolve => {
        doThing(thing, () => {
          resolve();
        });
      });
    });
  }, Promise.resolve());
}
queue(['lots', 'of', 'things', ....]);

Promise.resolve()解析

Promise.resolve()返回一个fulfilled状态的Promise实例,或原始的Promise实例,具有如下特点:

  • 参数为空,返回一个状态为fulfilled的Promise实例
  • 参数是一个跟Promise无关的值,同上,不过fulfilled响应函数会得到这个参数
  • 参数为Promise实例,则返回该实例,不做任何修改
  • 参数为thenble,则立刻执行它的then()
    看以下代码逐一分析
console.log('start');
Promise.resolve().then(() => {
  console.log('Step 1');
  return Promise.resolve('Hello');
}).then(value => {
  console.log(value, 'World');
  return Promise.resolve(new Promise(resolve => {
    setTimeout(() => {
      resolve('Good');
    }, 2000);
  }));
}).then(value => {
  console.log(value, ' evening');
  return Promise.resolve({
    then() {
      console.log(', everyone');
    }
  })
})

Promise.reject()解析

Promise.reject()除了不认thenable,其他的特点都和Promise.resolve()类似,请看如下代码:

let promise = Promise.reject('something wrong');
promise.then(() => {
  console.log('it\'s ok');
}).catch(() => {
  console.log('no, it\'s not ok');
  return Promise.reject({
    then() {
      console.log('it will be ok');
    },
    catch() {
      console.log('not yet');
    }
  });
});

Promise.race()解析

类似Promise.all(),区别在于它有任意一个完成就算完成,观察以下代码:

console.log('start');
let p1 = new Promise(resolve => {
  // 这是一个长时间的调用
  setTimeout(() => {
    resolve('I\'m P1');
  }, 10000);
});
let p2 = new Promise(resolve => {
  // 这是个稍短的调用
  setTimeout(() => {
    resolve('I\'m P2');
  }, 2000)
});
Promise.race([p1, p2]).then(value => {
  console.log(value);
});

Promise.race()常见用法是把异步操作和定时器放在一起,如果定时器先触发,就认为超时,告知用户。这里可能说的有点抽象,希望来这里看一看——大白话讲解Promise,那么很容易就能明了。

现实生活中的Promise应用

  • 把回调函数包装成Promise,使其可读性更好和返回的结果可以加入任何Promise队列
  • 把任何异步操作包装成Promise,假设有这需求:用户点击按钮,弹出确认窗体 ==> 用户确认和取消有不同的处理。那么就能编写如下代码:
// 弹出窗体
let confirm = popupManager.confirm('您确定么?');
confirm.promise.then(() => {
  // do confirm staff
}).catch(() => {
  // do cancel staff
});
// 窗体的构造函数
class Confirm {
  constructor() {
    this.promise = new Promise((resolve, reject) => {
      this.confirmButton.onClick = resolve;
      this.cancelButton.onClick = reject;
    })
  }
}

尾声

呃呃,IE那块我选择不鸟它了,一个连它爸爸都嫌弃的浏览器也是没救了。还有最新的异步函数async和await,不说了,困得一批,劳资下班睡觉去。

最后的最后,强烈推荐一篇反复出现的博文和那位博主,有非常值得学习的地方:大白话讲解Promise

CSS定位 position

前言

该篇博文是根据慕课网教程——《css定位position》整理而来,内容虽然简单基础,但是整理记录一下,帮助新手的同时也务实一下自己的基础知识。

HTML中三种布局方式

1、标准流:这是默认的布局方式。特点就是块级元素独占一行,意思就是两个块级或者多个块级元素不能在同一行显示,块级元素能设置宽高;内联元素能够在同一行显示,内联元素不能设置宽高。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style>
      div, p {
        height: 50px;
        line-height: 50px;
        background-color: green;
      }
      span, a {
        height: 50px;
        line-height: 50px;
        background-color: green;
      }
    </style>
  </head>
  <body>
    <div>我是div</div>
    <p>我是p</p>
    <span>我是span</span>
    <a>我是a</a>
  </body>
</html>

2、position定位属性

  • static:默认值,没有定位特性,元素出现在正常的流中。
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style>
      div {
        height: 50px;
        width: 200px;
        line-height: 50px;
        background-color: green;
        position: static;
        left: 20px;
        top: 20px;
      }
    </style>
  </head>
  <body>
    <div>我是div</div>
  </body>
</html>
  • relative:相对定位,相对于自身正常的位置进行定位,不脱离文档流。
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style>
      div {
        height: 50px;
        width: 200px;
        line-height: 50px;
        background-color: green;
      }
      p {
        position: relative;
        top: 0;
        left: 15px;
        height: 50px;
        width: 500px;
        line-height: 50px;
        background-color: brown;
      }
    </style>
  </head>
  <body>
    <div>我是div</div>
    <p>我是相对定位的p元素,根据自身所在的位置进行定位</p>
  </body>
</html>
  • absolute:绝对定位,相对于static定位以外的第一个父元素进行定位
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style>
      div {
        height: 500px;
        width: 800px;
        line-height: 500px;
        background-color: green;
        margin-left: 55px;
      }
      p {
        position: absolute;
        top: 0px;
        left: 55px;
        height: 50px;
        width: 500px;
        line-height: 50px;
        background-color: brown;
      }
    </style>
  </head>
  <body>
    <div>
    	<p>我是绝对定位的p元素,定位规则较复杂</p>
    </div>    
  </body>
</html>

当我们把父元素设置为相对定位或者绝对定位的时候,那么内部绝对定位的子元素就会根据父元素进行定位。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style>
      div {
        height: 500px;
        width: 800px;
        line-height: 500px;
        background-color: green;
        margin-left: 55px;
        position: relative;
      }
      p {
        position: absolute;
        top: 0px;
        left: 55px;
        height: 50px;
        width: 500px;
        line-height: 50px;
        background-color: brown;
      }
    </style>
  </head>
  <body>
    <div>
      <p>我是绝对定位的p元素,定位规则较复杂</p>
    </div>
  </body>
</html>
  • inherit:从父元素中继承position的属性值
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style>
      div {
        height: 500px;
        width: 800px;
        line-height: 500px;
        background-color: green;
        margin-left: 55px;
        position: relative;
      }
      p {
        position: inherit;
        top: 0px;
        left: 55px;
        height: 50px;
        width: 500px;
        line-height: 50px;
        background-color: brown;
      }
    </style>
  </head>
  <body>
    <div>
      <p>我是继承父元素相对定位的p元素</p>
    </div>
  </body>
</html>
  • fixed:固定定位,相对于浏览器窗口定位。
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
    <style>
      div {
        width: 100%;
        height: 3000px;
        border: 1px solid brown;
      }
      p {
        height: 50px;
        width: 500px;
        line-height: 50px;
        text-align: center;
        background-color: green;
        color: #fff;
        position: fixed;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }
    </style>
  </head>
  <body>
    <div>
      <p>我是固定定位元素,相对于浏览器窗口进行定位</p>
    </div>
  </body>
</html>

z-index详解

  • 一般情况下,两个position属性值为relative或absolute或fixed的元素发生重叠,那么在文档流中后面的元素会覆盖掉前面的元素
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
    <style>
      div {
        width: 300px;
        height: 300px;
        background-color: red;
        position: fixed;
        left: 33px;
        top: 55px;
      }
      p {
        width: 200px;
        height: 200px;
        background-color: green;
        position: fixed;
        left: 155px;
        top: 200px;
      }
    </style>
  </head>
  <body>
    <div>我是div</div>
    <p>我是p</p>
  </body>
</html>
  • 我们能够使用z-index属性改变元素之间的堆叠顺序
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
    <style>
      div {
        width: 300px;
        height: 300px;
        background-color: red;
        position: fixed;
        left: 33px;
        top: 55px;
        z-index: 1;
      }
      p {
        width: 200px;
        height: 200px;
        background-color: green;
        position: fixed;
        left: 155px;
        top: 200px;
      }
    </style>
  </head>
  <body>
    <div>我是div</div>
    <p>我是p</p>
  </body>
</html>
  • 父元素遮盖子元素的实现只能在子元素上动手脚,修改父元素的z-index值不会有任何的效果
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
    <style>
      div {
        width: 500px;
        height: 500px;
        background-color: red;
      }
      p {
        width: 200px;
        height: 200px;
        background-color: green;
        position: relative;
        z-index: -5;
      }
      h1 {
        width: 100px;
        height: 100px;
        background-color: black;
        position: absolute;
        top: 130px;
        left: 150px;
        z-index: -5;
      }
    </style>
  </head>
  <body>
    <div>
      <p>我是p</p>
      <h1></h1>
    </div>
  </body>
</html>
  • 子元素具有拼爹的特点,可以试一试修改的parent1和parent2的值
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
    <style>
      .parent1 {
        height: 500px;
        width: 500px;
        background-color: red;
        position: relative;
        z-index: 10;
      }
      .child1 {
        height: 300px;
        width: 300px;
        background-color: green;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }
      .parent2 {
        height: 400px;
        width: 400px;
        background-color: blueviolet;
        position: absolute;
        left: 50px;
        top: 50px;
        z-index: 5;
      }
      .child2 {
        height: 200px;
        width: 200px;
        background-color: teal;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }
    </style>
  </head>
  <body>
    <div class="parent1">
      <div class="child1">child1</div>
    </div>
    <div class="parent2">
      <div class="child2">child2</div>
    </div>
  </body>
</html>

提个小小的建议

感谢有幸看到楼主的这个repo,学到了许多知识 : )
提个小小的建议,首页README.md里的目录,如果用链接的方式会更方便查看读阅~

如扰请删~

Node开发实战知识整理

工具

介绍一款很方便的在线编辑器——webIDE,点击进入官网

前言

这是一本书的学习总结,书名为《Node.js开发实战详解》,作者为腾讯出身的黄丹华。由于这会是一篇很长的课程学习总结,为了优化大家的阅读体验,强烈建议安装Chrome浏览器的插件——GayHub。下载安装地址

第 1 章:Node.js基础知识

为什么使用Node

因为Node能处理高并发请求,而且由于Node.js是事件驱动,因此能够更好的节约服务器内存资源。同时,Node.js可以单独实现一个server,这也是Node一个非常大的优点,对于那些简单的server,通过Node实现比使用C++实现会简单得多。

最后,牢记Node解决了长连接、多请求引发的成本问题,因此在一些项目中,比如实时在线的游戏、实时聊天室、实时消息推送功能、实时监控系统等开发过程中,应该把握机会,应用Node来开发。

同步调用和异步调用

1、同步调用时一种阻塞式调用,一段代码调用另一段代码时,必须等待这段代码执行结束并返回结果后,代码才能继续执行下去。例如下面的代码

let n1 = 1
let n2 = 2
let n3 = 3
alert(n1)
alert(n2)
alert(n3)

2、异步调用是一种非阻塞式调用,一段异步代码还未执行完,可以继续执行下一段代码逻辑,当其他同步代码执行完之后,通过回调返回继续执行相应的逻辑,而不耽误其他代码的执行。例如下面的代码

let n1 = 1
let n2 = 2
let n3 = 3
alert(n1)
setTimeout(function() {
  alert(n2)
}, 2000)
alert(n3)

当然,关于异步还有另外一个例子,这个栗子也引出了异步调用和回调这两个东东的概念,下面请看代码

function Person() {
  this.think = function(callback) {
    setTimeout(function() {
      console.log('想出来了!')
      callback()
    }, 5000)
  }
  this.answer = function() {
    console.log('我正在思考一个问题^_^')
  }
}
var person = new Person()
person.think(function() {
  console.log('花费5s,得到一个正确的思考')
})
person.answer()

回调和异步调用

首先明确一点,回调并非是异步调用,回调是一种解决异步函数执行结果的处理方法。在异步调用里,如果我们希望将执行的结果返回并且处理时,可以通过回调的方法解决。为了能够更好的区分回调和异步回调的区别,下面看一个简单的栗子

function waitFive(name, callbackfn) {
  var pus = 0
  var currentDate = new Date()
  while(pus < 5000) {
    var now = new Date()
    pus = now - currentDate
  }
  // 执行回调函数
  callbackfn(name)
}
// 定义回调函数echo()
function echo(name) {
  console.log(name)
}
// 调用waitFive方法
waitFive('回调函数被调用啦', echo)
console.log('略略略略')

以上代码是一个回调逻辑,但不是一个异步代码逻辑,因为其中并没有涉及Node的异步调用接口。从上面的代码结果可以看出回调和异步调用的区别,当waitFive()方法执行时,整个代码执行过程都会等待waitFive()方法的执行,而并非如异步调用那样:waitFive()方法未结束,还会继续执行console.log('略略略略')。这也说明了回调还是一种阻塞式调用。

获取异步函数的执行结果

异步函数往往不是直接返回执行结果,而是通过事件驱动的方式,将执行结果返回到回调函数中,之后在回调函数中处理相应的逻辑代码。

如何来理解以上的代码呢?请看下面一个代码案例

var dns = require('dns')
dns.resolve4('id.qq.com', function(err, address) {})
console.log(address)

dns.resolve4()是一个异步函数,由此带来的问题就是console.log(address)输出的结果是undefined,因为你懂得,异步嘛,对不对。

既然异步函数会出现这个问题,那么我们就可以使用回调函数去获取参数,下面请看代码

var dns = require('dns')
dns.resolve4('id.qq.com', function(err, address) {
  if(err) {
    throw err
  }
  console.log(address)
})

第 2 章:模块和NPM

Node模块的概念

  • 原生模块:是Node中API提供的原生模块,原生模块在启动时已经被加载了。
  • 文件模块:是一种动态加载模块,加载文件模块的工作主要由原生模块module来实现和完成。总而言之,原生模块在启动时已经被加载,而文件模块则需要通过调用Node中的require方法来实现加载。

需要了解一点的是,Node会对原生模块和文件模块都进行缓存,因此在第二次require该模块的时候,不会有重复开销去加载模块,只需要从缓存中读取相应模块数据即可。

原生模块的调用

使用Node提供的API——require来加载相应的Node模块,require加载成功后会返回一个Node模块对象,该对象拥有该模块的所有属性和方法,如下代码

var httpModule = require('http')
httpModule.createServer(function(req, res) {
  
}).listen(port)

以上就是一个简单的调用原生模块的方法,Node中其他原生模块的调用方法都是一样的,主要是学会如何查看Node的API文档,以及如何应用其中的模块提供的方法和属性。

文件模块调用

文件模块的调用和原生模块的调用方式基本一致,但是需要注意的是,其两者的加载方式存在一定的区别,原生模块不需要指定模块路径,而文件模块加载时必须指定文件路径。比如我们在项目中创建一个test.js文件,代码如下

exports.name = '能被调用的变量'
exports.happy = function() {
  console.log('能被调用的方法')
}

var yourName = '不能被调用的变量'
function love() {
  console.log('不能被调用的方法')
}

接着我们在同一个目录中创建diaoyong.js文件加载test.js这个文件模块,代码如下

var test = require('./test.js')
console.log(test)

以上代码也指明了,在文件模块中,只有exports和module.exports对象暴露给该外部的属性和方法,才能够通过返回的require对象进行调用,其他方法和属性是无法获取的。

Node原生模块实现web解析DNS

我们使用Node的原生模块和文件模块两个方法实现DNS解析工具,通过分析对比,来说明文件模块存在的必要性,以及其存在的必要性。

下面我们先看一下使用原生模块创建的DNS解析工具代码(先创建parse_dns_ex.js文件)

// 加载创建web的HTTP服务器模块
var http = require('http')
// 加载DNS解析模块
var dns = require('dns')
// 加载文件读取模块
var fs = require('fs')
// 加载URL处理模块
var url = require('url')
// 加载处理请求参数模块
var querystring = require('querystring')
http.createServer(function(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  // 获取当前html文件路径
  var readPath = __dirname + '/' + url.parse('2-1-3.html').pathname
  // 同步读取文件
  var indexPage = fs.readFileSync(readPath)
  res.end(indexPage)
}).listen(3000, '127.0.0.1')

接着我们创建一个html文件,代码如下

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>DNS查询</title>
  </head>
  <body>
    <h1 style="text-align: center;">DNS查询工具</h1>
    <div style="text-align: center;">
      <form action="/parse" method="post">
        <span>查询DNS:</span>
        <input type="text" name="search-dns" />
        <input type="submit" value="查询" />
      </form>
    </div>
  </body>
</html>

然后我们在命令行输入命令:node parse_dns_ex.js,就能够在浏览器输入http://127.0.0.1:3000,看到HTML页面内容。

由于以上代码只是实现了一个表单提交的页面,对于DNS解析部分还没有做任何处理,因此我们需要对js文件做如下的处理

var http = require('http')
var dns = require('dns')
var fs = require('fs')
var url = require('url')
var querystring = require("querystring")
http.createServer(function(req, res) {
  // 获取当前请求资源的url路径
  var pathname = url.parse(req.url).pathname
  // 设置编码格式,避免出现乱码
  req.setEncoding("utf8")
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  router(res, req, pathname)
}).listen(3000, "127.0.0.1")
console.log('Server running at http://127.0.0.1:3000/')
// 路由函数实现
function router(res, req, pathname) {
  switch(pathname) {
    case "/parse":
      parseDns(res, req)
      break;
    default:
      goIndex(res, req)
  }
}
// 解析域名函数实现
function parseDns(res, req) {
  var postData = ""
  req.addListener("data", function(postDataChunk) {
    postData += postDataChunk
  });
  req.addListener("end", function() {
    var retData = getDns(postData, function(domain, addresses) {
      res.writeHead(200, {
        'Content-Type': 'text/html'
      });
      res.end(`
        <html>
          <head>
            <title>DNS解析结果</title>
            <meta charset='utf-8'>
          </head>
          <body>
            <div style='text-align:center'>
              Domain:<span style='color:red'>${domain}</span>
              IP:<span style='color:red'>${addresses.join(',')}</span>
            </div>
          </body>
        </html>
      `)
    })
    return
  })
}
// 返回html文件函数实现
function goIndex(res, req) {
  var readPath = __dirname + '/' + url.parse('2-1-3.html').pathname
  // 同步读取html文件的信息
  var indexPage = fs.readFileSync(readPath)
  res.end(indexPage)
}
// 异步解析域名函数
function getDns(postData, callback) {
  var domain = querystring.parse(postData).search_dns;
  dns.resolve(domain, function(err, addresses) {
    if(!addresses) {
      addresses = ['不存在域名']
    }
    callback(domain, addresses)
  })
}

以上代码就能够实现当你输入www.qq.com的时候,显示它的IP。

Node文件模块实现web解析DNS

文件模块的好处在于将业务处理分离,每个模块处理相应的职责,避免业务混乱。接下来我们分析DNS解析系统需要划分哪些模块,以及这些模块之间的功能和作用分别是什么。下面就来看看各个模块的作用以及具体代码。

入口模块(index.js),创建http服务器处理客户端请求

// 加载原生http和url模块
var http = require('http')
var url = require('url')
// 加载文件模块之路由处理模块
var router = require('./router.js')
http.createServer(function(req, res) {
  // HTTP请求路径
  var pathname = url.parse(req.url).pathname
  req.setEncoding('utf8')
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  // router(res,req,pathname)是router文件模块中的exports方法
  router.router(res, req, pathname)
}).listen(3000, '127.0.0.1')
console.log('Server runing http://127.0.0.1:3000')

路由处理模块(router.js),处理所有请求资源,分发到相应处理器。说白了就是负责url转发以及请求资源分配。

// 加载文件模块之DNS解析模块
var ParseDns = require('./parse_dns.js')
// 加载文件模块之首页展示模块
var MainIndex = require('./main_index.js')
exports.router = function(res, req, pathname) {
  switch(pathname) {
    case '/parse':
      ParseDns.parseDns(res, req)
      break;
    default:
      MainIndex.goIndex(res, req)
  }
}

这里需要注意的是,router方法必须应用exports暴露给require返回的对象,如果不使用exports方法,相对于router.js文件模块来说就是私有方法,require router模块返回对象将无法调用。

DNS解析模块(parse_dns.js),DNS处理逻辑,根据获取的域名进行解析,返回相应的处理结果到页面,这部分代码和上面的原生模块的代码类似,主要是parseDns和getDns两个方法。

var querystring = require('querystring')
var dns = require('dns')
exports.parseDns = function(res, req) {
  var postData = ''
  req.addListener('data', function(postDataChunk) {
    postData += postDataChunk
  })
  req.addListener('end', function() {
    var retData = getDns(postData, function(domain, addresses) {
      res.writeHead(200, {
        'Content-Type': 'text/html'
      });
      res.end(`
        <html>
          <head>
            <title>DNS解析结果</title>
            <meta charset='utf-8'>
          </head>
          <body>
            <div style='text-align:center'>
              Domain:<span style='color:red'>${domain}</span>
              IP:<span style='color:red'>${addresses.join(',')}</span>
            </div>
          </body>
        </html>
      `)
    })
    return
  })
  // 获取post数据
  function getDns(postData, callback) {
    var domain = querystring.parse(postData).search_dns
    // 异步解析域名
    dns.resolve(domain, function(err, addresses) {
      if(!addresses) {
        addresses = ['不存在域名']
      }
      callback(domain, addresses)
    })
  }
}

以上的js代码对应html代码为

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
  </head>
  <body>
    <h1 style="text-align: center;">DNS查询工具</h1>
    <div style="text-align: center;">
      <form action="/parse" method="post">
        <span>查询DNS:</span>
        <input type="text" name="search_dns" />
        <input type="submit" value="查询" />
      </form>
    </div>
  </body>
</html>

首页展示模块(main_index.js),处理主页index.html页面的显示,使用fs模块进行读取index.html页面字符数据,然后返回到客户端。

var fs = require('fs')
var url = require('url')
exports.goIndex = function(res, req) {
  var readPath = __dirname + '/' + url.parse('index.html').pathname
  // 同步读取index.html页面数据
  var indexPage = fs.readFileSync(readPath)
  res.end(indexPage)
}

整个过程结束后,运行index.js同样可以实现跟原生模块DNS解析的作用。

exports和module.exports

两者的作用都是将文件模块的方法和属性暴露给require返回的对象进行调用。但是二者存在本质的区别:exports的属性和方法都可以被module.exports替代,反过来则不行。它们之间还有以下的不同点

  • module.exports方法还可以单独返回一个数据类型,而exports只能返回一个object对象,因此,当我们需要返回一个数组、字符串、数字等类型的时候,就必须使用module.exports
  • 当在exports前面使用了moudle.exports,那么exports的任何方法和属性都会失效,请看下面案例代码
index.js文件模块
module.exports = 'exports的属性和方法将被忽视!'
exports.name = '我无法被调用'
exports.showName = function () {
  console.log('我也无法被调用')
}
console.log('内部module.exports值被调用:' + module.exports)

// 调用index.js文件模块
var Book = require('./index.js')
console.log('调用Book:' + Book)
console.log('调用Book中的name:' + Book.name)
console.log('调用Book中的showName():' + Book.showName())

习题检测

(1)实现person.js文件模块,其返回的是一个person函数,该函数中有eat和say方法

module.exports = function() {
  this.eat = function(){}
  this.say = function(){}
}

(2)实现person.js文件模块,其返回的是一个eat方法和say方法的对象

exports.Person = {
  'eat':function() {},
  'say':function() {}
}

(3)实现person.js文件模块,其返回的是一个数组,数组内容为人名

module.exports = ['jack', 'tom', 'lucy']

(4)实现person.js文件模块,其返回的是一个对象,该对象中包含一个数组元素

exports.arr = ['jack', 'tom', 'lucy']

使用Express

express是一个Node.js的web开源框架,该框架可以快速搭建web项目开发的框架。其主要集成了web的http服务器的创建、静态文件管理、服务器url请求处理、get和post请求分支、session处理等功能。下面是使用express的步骤

  • 安装: npm install -g express
  • 安装依赖: npm install -g express-generator
  • 创建一个app应用: express 项目名称
  • 进入到项目中: cd 项目名称
  • 安装jar包: npm install
  • 运行项目: npm start 或者DEBUG=nodedemo:* npm start,总之按照提示走就对了
  • 访问地址:http://127.0.0.1:3000/

查看当前版本:express --version

使用jade模块

该模块的作用就是可以内嵌其他代码到html页面中,比如在html页面中内嵌php代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <?php
	  $name = 'hello php';
	  echo $name;
    ?>
  </body>
</html>

下面我们在一个express项目中使用jade模块。首先创建一个jade.js文件,记得事先安装(npm install jade)好了jade模块

var express = require('express');
var http = require('http');
var app = express();
// 设置模板引擎
app.set('view engine', 'jade');
// 设置模板相对路径(相对当前目录)
app.set('views', __dirname);
app.get('/', function(req, res) {
  // 调用当前路径下的 test.jade 模板
  res.render('test');
})
var server = http.createServer(app);
server.listen(3002);
console.log('server started on http://127.0.0.1:3002/');

接着,创建一个test.jade模板

- console.log('hello'); // 这段代码在服务端执行
- var s = 'hello world' // 在服务端空间中定义变量
p #{s}
p= s

最后,在命令行终端运行:node jade.js。关于更多jade知识,可以点击这里进行学习。

习题检测

在安装好jade模块的express项目中,修改routes/index.js文件,传递一个名为info的json对象,json值为{'name':'jack','book':'Node.js'}。同时在views/index.jade页面使用div展示name,使用h2标签展示book

var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index.jade', {
    name: 'jack',
    book: 'Node.js',
    title: 'a'
  })
});
module.exports = router
 

extends layout
block content
  h2 #{book}
  p Welcome to #{name}

使用forever模块

服务器管理是系统上线后,必须要面对的问题。最好有一个软件可以提供整套的服务器运行解决方案:要求运行稳定,支持高并发,启动/停止命令简单,支持热部署,宕机重启,监控界面和日志,集群环境。接下来,就让我们使用forever模块来实现以上的要求。

使用步骤

  • 安装:npm install forever -g
  • 查看模块内置命令: forerver --help
  • 运行express框架自带的app.js文件:forever start -l forever.log -o out.log -e err.log app.js
-l指定forever运行日志,-o指定脚本流水日志,
-e指定脚本运行错误日志。启动后将会在本项目中产生out.log、err.log文件
  • 查看node进行返回结果:netstat

更多关于forever模块的知识请点击这里,不过到了现在,该模块貌似被PM2给取代了,有兴趣的去看看吧。

使用socket.io模块

socket.io是一个基于Node.js的项目,其作用主要是将WebSocket协议应用到所有的浏览器。该模块主要应用于实时的长连接、多请求项目中,例如在线互联网游戏、实时聊天、实时股票查看、二维码扫描登录等。

由于书中给的案例并没有跑起来,因此只将其代码抄了下来,相关的解释说明等过后再说

// 设置监听端口
var io = require('socket.io').listen(8080)
// 当客户端connection时,执行回调函数
io.sockets.on('connection', function(socket) {
  // 连接成功后发送一个news消息,消息内容为json对象
  socket.emit('news', {
    hello: 'world'
  })
  // 客户端发送my other event消息时,服务器端接受该消息
  // 成功获取该消息后执行回调函数
  socket.on('my other event', function(data) {
    console.log(data)
  })
})

以及客户端代码

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>socket</title>
  </head>
  <body>
    <script src="socket.js"></script>
    <script>
      // 使用socket连接本地socket服务器
      var socket = io.connect('http://localhost:8080')
      // 接受到服务器发送的news消息后,当服务器推送news消息后执行回调函数
      socket.on('news', function(data) {
        console.log(data)
        // 客户端接受news消息成功后,发送my other event消息到
        // 服务器,其发送的消息内容为json对象
        socket.emit('my other event', {
          my: 'data'
        })
      })
    </script>
  </body>
</html>

使用request模块

request模块为Node.js开发者提供了一种简单访问HTTP请求的方法。在开始之前我们需要进行模块安装npm install request,不过在安装之前要记得先使用npm init初始化一个package.json文件,否则模块是安装不上去的。

下面我们来看重点内容,get和post请求。要牢记的是:get和post只是发送机制不同,并不是一个取一个发!

首先看看处理get请求,首先创建一个HTTP服务器发出一个请求(app_get.js)

// 创建一个http服务器
var http = require('http')
http.createServer(function(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/plain'
  })
  // 返回一个字符 ,并且打印出请求的方式
  res.end('Hello World\n' + req.method)
}).listen(1337, "127.0.0.1")
console.log('服务运行地址=> http://127.0.0.1:1337/');

然后通过request模块去请求该服务器数据,并将服务器返回结果打印出来(request_get.js)

var request = require('request')
request.get('http://127.0.0.1:1337', function(error, response, result) {
  console.log(result)
})

应用request模块的get方法,发起一个HTTP请求,请求本地http://127.0.0.1:1337服务器数据,request.get()方法中的两个参数分别是请求url和回调函数。

接下来运行node app_get.js代码,启动服务器,可以到浏览器看看输出的内容;然后再打开一个命令行(记得之前启动服务器的命令行不要关闭),运行node request_get.js代码,可以看到从服务器中请求回来的数据。

下面我们再来看一看关于post请求的一些代码,和get方式类似,也是先创建一个HTTP服务器(app_post.js),该服务器接收客户端的post参数,并将该参数作为字符串响应到客户端

var http = require('http')
// querystring从字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析
var querystring = require('querystring')
http.createServer(function(req, res) {
  var postData = ""
  // 开始异步接收客户端post的数据
  req.addListener("data", function(postDataChunk) {
    postData += postDataChunk
  })
  // 异步post数据接收完成后执行匿名回调函数
  req.addListener("end", function() {
    // 解析客户端发送的post数据,并将其转化为字符
    var postStr = JSON.stringify(querystring.parse(postData))
    res.writeHead(200, {
      'Content-Type': 'text/plain'
    })
    // 响应客户端请求的post数据
    res.end(postStr + '\n' + req.method)
  })
}).listen(1400, "127.0.0.1")
console.log('Server running at http://127.0.0.1:1400/')

接着应用request模块发起HTTP的post请求,将数据传到HTTP服务器中,然后又取得服务器返回的数据(request_post.js)

var request = require('request')
request.post('http://127.0.0.1:1400', {
  // form意思是表单数据
  form: {
    'name': 'danhuang',
    'book': 'node.js'
  }
}, function(error, response, result) {
  console.log(result)
});

接下来运行node app_post.js代码,启动服务器,可以到浏览器看看输出的内容;然后再打开一个命令行(记得之前启动服务器的命令行不要关闭),运行node request_post.js代码,可以看到从服务器中返回来处理过的数据,同时将HTTP请求方式也响应到客户端。

使用Formidable模块

formidable模块实现了上传和编码图片和视频。它支持GB级上传数据处理,支持多种客户端数据提交。有极高的测试覆盖率,非常适合在生产环境中使用。

在原生的Node.js模块中,提供了获取post数据的方法,但是没有直接获取上传文件的方法,因此需要利用formidable模块来处理文件上传逻辑。接下来我们看一下实例(记得先npm init,然后npm install formidable)

var formidable = require('formidable')
var http = require('http')
var util = require('util')
// 创建一个HTTP服务器
http.createServer(function(req, res) {
  // 判断请求路径是否为upload,如果是则执行文件上传处理逻辑
  // 同时还判断HTTP请求方式是否为post
  if(req.url == '/upload' && req.method.toLowerCase() == 'post') {
    // 创建form对象
    var form = new formidable.IncomingForm()
    // 解析post数据
    form.Parse(req, function(err, fields, files) {
      res.writeHead(200, {
        'content-type': 'text/plain'
      })
      res.write('received upload:\n\n')
      // 将json对象转化为字符串
      res.end(util.inspect({
        fields: fields,
        files: files
      }))
    })
    return
  }
  res.writeHead(200, {
    'content-type': 'text/html'
  })
  res.end(`
	<form action='/upload' enctype='multipart/form-data' method='pst'>
	  <input type='text' name='title'><br>
	  <input type='file' name='upload' multiple='multiple'><br>
	  <input type='submit' vaule='upload'>
	</form>
  `)
}).listen(8080)

之后运行这个项目,上传一张图片即可看到效果。

开发一个自己的NPM模块

  • 步骤一:创建一个项目
  • 步骤二:初始化一个package.json文件(npm init)
  • 步骤三:创建一个index.js文件
function testNpm(name) {
  console.log(name)
}
exports.testNpm = testNpm
  • 步骤四:创建一个test.js文件
var fn = require('./index.js')
fn.testNpm('hello,这是测试模块')
  • 步骤五:执行test.js文件,看看是否有错误(node test.js)
  • 步骤六:来到官网注册自己的开发账号
  • 步骤七:使用命令行连接NPM,输入自己的注册信息
$ npm adduser
Username: fengxiong
Password: 9*****8*q
Email: (this IS public) 9*****[email protected]
Logged in as fengxiong on https://registry.npmjs.org/.

$ npm whoami
fengxiong
  • 注意:如果出现403错误,那么很有可能是邮箱尚未验证或者仓库地址使用了淘宝镜像,点击这里查看解决方法
  • 步骤八:发布模块(npm publish)
  • 步骤九:下载使用该模块。
1、创建一个新项目
2、初始化一个package.json文件
3、安装自己的模块:npm install --save "原先模块package.json文件中的name值"

最后是下载该模块的项目结构
image

我们可以在根目录的test.js文件中这么来写

var fn = require('./node_modules/fengxiong/index.js')
fn.testNpm('hello,这是测试模块')

然后在运行node test.js,那么就能得到相应的结果。

模块与类

模块是程序设计中,为完成某一功能所需的一段程序或者子程序;或者指能由编译程序,装配程序等处理的独立程序单位;或者指大型软件系统的一部分。

而在Node.js中可以理解为完成某一功能所需的程序或者子程序,同时也可以将Node.js的一个模块理解为一个“类”,但注意,其本身并非是类,而只是简单意义上的一个对象,该对象拥有多个方法和属性,Node.js模块也拥有私有成员和公有成员。

一般来说exports和module.exports的成员为公有成员,而非exports和module.exports的成员为私有成员。

Node.js中的继承

继承的方式主要是通过Node.js的util模块inherits的API来实现继承,将一个构造函数的原型方法继承到另一个构造函数中。

constructor构造函数的原型将被设置为使用superConstructor构造函数所创建的一个新对象。可以看看下面的栗子,其目的就是使用MyStream继承events.EventEmitter对象

// 获取util和event模块
var util = require("util")
var events = require("events")

// 使用MyStream来继承events.EventEmitter方法属性
function MyStream() {
  events.EventEmitter.call(this)
}

// 应用inherits来实现Mystream继承EventEmitter
util.inherits(MyStream, events.EventEmitter)
// 为MyStream类添加方法
MyStream.prototype.write = function(data) {
  this.emit("data", data)
}

// 创建一个MyStream对象
var stream = new MyStream()
// 判断是否继承了events.EventEmitter
console.log(stream instanceof events.EventEmitter)
// 通过super_获取父类对象
console.log(MyStream.super_ === events.EventEmitter)

// 调用继承来自events.EventEmitter的方法
stream.on("data", function(data) {
  console.log("接收到的数据是:" + data)
})
stream.write("现在是2018-08-29 21:31")

上面的栗子可能稍微抽象些,下面我搞一个比较具体的栗子,比如:学生、老师、程序员继承人这个类,实现学生学习、老师教书、程序员写代码的功能。首先,我们创建一个Person基类作为人

module.exports = function() {
  this.name = 'person'
  // 定义sleep方法
  this.sleep = function() {
    console.log('入夜了,需要睡觉')
  }
  // 定义eat方法
  this.eat = function() {
    console.log('肚子饿了,吃点东西')
  }
}

下面我们再创建一个Student类继承Person类

var util = require("util")
var Person = require("./person")
// 定义Student类
function Student() {
  Person.call(this)
}
// 将Student类继承Person
util.inherits(Student, Person)
// 重写study方法
Student.prototype.study = function() {
  console.log('我正在学习')
}
// 暴露Student类
module.exports = Student

这样就实现了student继承person类,同时新增类的自我方法study。下面我们再来看看实现一个Teacher类继承Person类

var util = require('util')
var Person = require('./person')
// 定义Teacher类
function Teacher() {
  Person.call(this)
}
// 将Teacher类继承Person类
util.inherits(Teacher, Person)
// 重写teach方法
Teacher.prototype.teach = function() {
  console.log('我正在教学')
}
// 暴露Teacher类
module.exports = Teacher

为了加深下印象,我们再写一个Coder类继承Person类

var util = require("util")
var Person = require("./person")
// 定义个Coder类
function Coder() {
  Person.call(this)
}
// 继承
util.inherits(Coder, Person)
// 重写code方法
Coder.prototype.code = function() {
  console.log("我正在敲代码")
}
// 暴露Coder类
module.exports = Coder

下面我们进入重头戏,看看如何调用继承类,创个app.js文件

var Person = require('./person')
var Student = require('./student')
var Teacher = require('./teacher')
var Coder = require('./coder')
// 创建对象
var personObj = new Person()
var studentObj = new Student()
var teacherObj = new Teacher()
var coderObj = new Coder()

// 执行personObj对象的所有方法
console.log('---Person类---')
personObj.sleep()
personObj.eat()
console.log('----------')
// 执行studentObj对象的所有方法
console.log('---Student类---')
studentObj.sleep()
studentObj.eat()
studentObj.study()
console.log('----------')
// 执行teacherObj对象的所有方法
console.log('---Teacher类---')
teacherObj.sleep()
teacherObj.eat()
teacherObj.teach()
console.log('----------')
// 执行coderObj对象的所有方法
console.log('---Coder类---')
coderObj.sleep()
coderObj.eat()
coderObj.code()
console.log('----------')

之后在终端执行命令:node app.js,那么就可以看到继承的效果出现了。那么该如何改写父类定义好的方法呢?其实很简单,只要直接覆盖就行了,下面就来看看在Student类中重写父类的eat()方法

var util = require("util")
var Person = require("./person")
// 定义Student类
function Student() {
  Person.call(this)
  this.eat = function() {
    console.log('身为一个学生,肚子饿了也得忍着')
  }
}
// 将Student类继承Person
util.inherits(Student, Person)
// 重写study方法
Student.prototype.study = function() {
  console.log('我正在学习')
}
// 暴露Student类
module.exports = Student

动态类对象和静态类对象

Node.js中可以应用module.exports实现一个动态类对象,那么Node.js中如何实现一个静态类对象呢?例如:假设有一个基类Person,其有继承类Student,但是我们希望使用静态类对象的方式调用Student中的方法和属性,而不希望new一个对象。可以结合exports以及继承方法来实现静态类。

首先,我们先使用动态类对象调用的方式实现一下代码

// 基类Person
module.exports = function() {
  this.name = 'person'
  // 定义sleep方法
  this.sleep = function() {
    console.log('入夜了,需要睡觉')
  }
  // 定义eat方法
  this.eat = function() {
    console.log('肚子饿了,吃点东西')
  }
}

// 子类Student
var util = require("util")
var Person = require("./person")
// 定义Student类
function Student() {
  Person.call(this)
  // 将Student类继承Person
  util.inherits(Student, Person)
  this.study = function() {
    console.log('我正在学习')
  }
}
// 暴露Student类
module.exports = Student

// 动态调用Student类的方式
var Student = require('./student')
var student = new Student()
student.study()

现在我们使用静态类对象的调用方式,调用Student中的对象,那么我们可以将student这个模块实现方式修改为如下代码(这一处书中代码有误,我修改过来了):

// 子类Student
var util = require("util")
var Person = require("./person")
// 定义Student类
function Student() {
  Person.call(this)
  // 将Student类继承Person
  util.inherits(Student, Person)
  this.study = function() {
    console.log('我正在学习')
  }
}
var student = new Student()
// 暴露以下的方法
exports.study = student.study
exports.eat = student.eat
exports.sleep = student.sleep

// 静态调用Student类的方式
var student = require('./student')
student.study()
student.eat()
student.sleep()

通过在类定义模块中new一个本身对象,并将该对象的方法全部通过exports暴露给外部接口,就无需在每次调用该类的地方new一个该对象了。因此在使用上就可以将student这个类看成一个静态类

这种方法实现很简单,也很容易理解,在很多时候非常有用。这样做的好处是可以避免代码的冗余,当student这个类被多个地方调用时,如果是动态调用的话,就必须每次都去new一个该对象,而如果使用类静态方法调用时,就可以直接通过require返回的对象进行调用。

当然,不是所有的类都可以这样去调用,如果每次在该类的内部new一个对象都需要初始化一些参数变量,那么就可以选择使用静态调用方法。

习题练习

实现一个基类animal,该基类包含方法say,该方法输出内容,接下来实现两个继承类duck和bird,其中duck是一个静态类模块,其有方法say,该方法输出ga..ga...ga..,而bird则是一个动态调用模块,其有方法输出ji...ji...ji...

// 基类
module.exports = function() {
  this.say = function() {
    console.log('动物特性')
  }
}

// 子类A
var util = require('util')
var Animal = require('./animal')
function Duck() {
  // 应用arguments对象获取函数参数
  var _res = arguments[0]
  var _req = arguments[1]
  // 把animal对象中的this指向绑定到Duck对象中
  Animal.call(this)
  // 继承
  util.inherits(Duck, Animal)
  this.say = function() {
    console.log('ga..ga..ga..')
  }
}
// 暴露方法给外部接口进行调用
var duck = new Duck()
exports.say = duck.say

// 子类B
var util = require('util')
var Animal = require('./animal')
function Bird() {
  var _res = arguments[0]
  var _req = arguments[1]
  Animal.call(this)
  util.inherits(Bird, Animal)
  this.say = function() {
    console.log('ji...ji...ji...')
  }
}
module.exports = Bird

// 测试类
var duck = require('./duck')
var Bird = require('./bird')
duck.say()
var bird = new Bird()
bird.say()

单例模式

一般认为单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在,就直接返回,如果不存在,则会创建该对象,并将该对象保存在静态变量中,当下次请求时,则可以直接返回该对象,这就确保了一个类只有一个实例对象。

下面请看一个例子,使用私有变量记录new的相应对象

// 单例类文件
// 舒适化一个私有变量
var _instance = null
module.exports = function(time) {
  // 创建Class类
  function Class(time) {
    this.name = 'no锋'
    this.book = 'Node.js'
    this.time = time
  }
  // 创建类方法
  Class.prototype = {
    constructor: Class,
    show: function() {
      console.log(`《${this.book}》这本书是${this.name}${this.time}编写的`)
    }
  }
  // 获取单例对象接口
  this.getInstance = function() {
    if(_instance === null) {
      _instance = new Class(time)
    }
    return _instance
  }
}

注意,_instance变量是要放在单例方法之外,否则无法实现单例模式。原因是当调用单例方法时每次都会重新将其赋值为null,而放在单例函数之外时,调用单例函数不会影响到_instance变量的值。

接下来,我们再创建一个js文件来调用类对象

var Single = require('./student')
var singleObjOne = new Single('2018-09-02')
var singleClassOne = singleObjOne.getInstance('2018-09-02')
singleClassOne.show()

var singleObjTwo = new Single('2018-09-01')
var singleClassTwo = singleObjTwo.getInstance('2018-09-01')
singleClassTwo.show()

从输出结果可以看出来,第二次new单例对象的时候,没有创建新的Class类对象,而是返回了第一次创建的Class类对象。这样就应用Node.js实现了单例模式。

适配器模式

若将一个类的接口转换成客户希望的另外一个接口,Adapter(适配器)模式可以使原本由于接口不兼容而不能一起工作的那些类可以一起工作。下面我们直接看案例代码

// Target父类
module.exports = function() {
  this.request = function() {
    console.log('这是父类的request方法')
  }
}

// Adaptee类
module.exports = function() {
  this.specialRequest = function() {
    console.log('我才是被子类真正调用的方法')
  }
}

// Adapter子类
var util = require('util')
var Target = require('./target')
var Adaptee = require('./adaptee')
// 定义Adapter函数类
function Adapter() {
  Target.call(this)
  this.request = function() {
    var adapteeObj = new Adaptee()
    adapteeObj.specialRequest()
  }
}
// Adapter类继承Target类
util.inherits(Adapter, Target)
// 暴露Adapter类
module.exports = Adapter

// 一个测试脚本文件
var Adapter = require('./adapter')
var target = new Adapter()
target.request()

从运行结果可以看到,其通过适配器调用了Adaptee中的specialRequest方法,这样就实现了Node.js中的适配器模式。

装饰模式

装饰模式就是动态的给一个对象添加一些额外的职责,就扩展功能而言,它比生成子类方式更为灵活。下面我们就先创一个Component父类

module.exports = function() {
  this.operation = function() {
    console.log('这是父类的operation方法')
  }
}

接着在创建一个子类ConcreteComponent,其作用就是展示父类装饰之前类中的属性和方法,重定义operation方法

var util = require('util')
var Component = require('./component')
// 定义函数类
function ConcreteComponent() {
  Component.call(this)
  this.operation = function() {
    console.log('这是子类的operation方法')
  }
}
// 继承父类component
util.inherits(ConcreteComponent, Component)
// 暴露ConcreteComponent类
module.exports = ConcreteComponent

然后再创建一个Decorator基类,用于装饰Component类

var util = require('util')
var Component = require('./component')
function Decorator() {
  Component.call(this)
}
util.inherits(Decorator, Component)
module.exports = Decorator

创建ConcreteDecoratorA装饰类,该类的目的是为Component类的operation方法提供一些额外的操作,比如添加一些额外的计算规则和输出一些额外的数据

var util = require('util')
var Decorator = require('./decorator')
function ConcreteDecoratorA() {
  Decorator.call(this)
  this.operation = function() {
    // 调用被装饰类的operation基本方法
    Decorator.operation
    console.log('为父类的父类提供额外的一些操作')
  }
}
util.inherits(ConcreteDecoratorA, Decorator)
module.exports = ConcreteDecoratorA

创建ConcreteDecoratorB装饰类,该类的目的是为Component类的operation方法提供一些额外的操作,同时添加新的功能方法

var util = require('util')
var Decorator = require('./decorator')
function ConcreteDecoratorB() {
  Decorator.call(this)
  this.operation = function() {
    // 调用被装饰类的operation基本方法
    Decorator.operation
    console.log('继续为父类的父类提供额外的一些操作')
  }
  this.addedBehavior = function() {
    console.log('装饰类Component添加新的行为动作')
  }
}
util.inherits(ConcreteDecoratorB, Decorator)
module.exports = ConcreteDecoratorB

最后我创建一个测试类

var ConcreteDecoratorA = require('./concreteDecoratorA')
var ConcreteDecoratorB = require('./concreteDecoratorB')
var target = new ConcreteDecoratorA()
target.operation()

var targetone = new ConcreteDecoratorB()
targetone.operation()
targetone.addedBehavior()

装饰类的应用场景是在不改变基类的情况下,为基类新增属性和方法。

工厂模式

定义一个用于创建对象的接口,让子类决定将哪一个类实例化,工厂模式就是使一个类的实例化延迟到其子类。下面我们编写一个基类Product

module.exports = function() {
  this.getProduct = function() {
    console.log('这个是父类的getProduct方法')
  }
}

创建两个子类ProductA和ProductB,分别重写父类的getProduct方法

// 子类ProductA
var util = require('util')
var Product = require('./product')
function ProductA() {
  Product.call(this)
  this.getProduct = function() {
    console.log('这是子类A的getProduct方法')
  }
}
util.inherits(ProductA, Product)
module.exports = ProductA

// 子类ProductB
var util = require('util')
var Product = require('./product')
function ProductB() {
  Product.call(this)
  this.getProduct = function() {
    console.log('这是子类B的getProduct方法')
  }
}
util.inherits(ProductB, Product)
module.exports = ProductB

接着创建工厂对象productFactory,根据不同的参数获取不同的Product对象。这里需要注意的是,createProduct使用exports而不是使用module.exports,目的是传递一个ProductFactory对象,而非一个ProductFactory类

var ProductA = require('./productA')
var ProductB = require('./productB')
exports.createProduct = function(type) {
  switch(type) {
    case 'ProductA' : return new ProductA
    break
    case 'ProductB' : return new ProductB
    break
    default : return false
  }
}

最后创建一个测试文件

var ProductFactory = require('./productFactory')
var ProductA = ProductFactory.createProduct('ProductA')
ProductA.getProduct()

var ProductB = ProductFactory.createProduct('ProductB')
ProductB.getProduct()

从结果可以看出通过传递不同的字符串,获取了不同的对象ProductA和ProductB,在工厂模式中还包括工厂方法和抽象工厂两个模式。

JavaScript进阶学习总结与资料

前言

该份资料的来源为慕课网教程《JavaScript深入浅出》,内容几乎是全文摘抄下来,不喜勿喷啊。

数据类型

JavaScript被称为是一种弱类型的语言,原因就是数据类型居然能够随意转换而不报错,而且在定义变量的时候不用指定其类型,示例代码如下:

var num = 32;
num = "this is a string";

面试题常问的:JavaScript中原始类型有哪几种? 答:number、string、boolean、null、undefined。

隐式转换

1、加号(+)和减号(-)
在数字与字符串做运算的时候,加号做拼接,减号就做减法

"37" - 7   //30
"37"+7   //377

2、等于号(==)
等于号判断原始类型的时候会有自动类型转换的特点,在判断引用类型的时候会从引用地址上进行判断。

"1.23" == 1.23   //true
0 == false   //true
null == undefined   //true
new Object() == new Object()   //false
[1, 2] == [1, 2]   //false
new String('hi') == 'hi'   //true
new String('456') == 456    //true

3、严格等于(===)
相比于等于号,严格等于不会进行类型转换,它会一开始判断两者之间的类型,如果类型不同,直接返回false。类型相同且内容相同才返回true。

"1.23" === 1.23   //false
0 === false   //false
null === undefined   //false
new Object() === new Object()   //false
[1, 2] === [1, 2]   //false
new String('hi') === 'hi'   //false
new String('456') === 456    //false

包装对象

JavaScript是面向对象的语言,使用”.”操作符可以访问对象的属性和方法,而对于基本类型(null,undefined, bool, number, string)应该是值类型,没有属性和方法,然而

var str = "this is a string";
console.log(str.length); //16
console.log(str.indexOf("is")); //2

结果很简单,但是仔细想想还真奇怪,string不是值类型吗!怎么又有属性又有方法的!其实只要是引用了字符串的属性和方法,JavaScript就会将字符串值通过new String(s)的方式转为内置对象String,一旦引用结束,这个对象就会销毁。所以上面代码在使用的实际上是String对象的length属性和indexOf方法。

同样的道理,数字和布尔值的处理也类似。null和undefined没有对应对象。既然有对象生成,能不能这样

var str = "this is a string";
str.b = 10;
console.log(str.b);  //undefined

结果并没有返回10,而是undefined!不是说好了是个对象吗!正如刚才提到第二行代码只是创建了一个临时的String对象,随即销毁,第三行代码又会创建一个新的临时对象(这就是低版本IE频繁处理字符串效率低的一个原因),自然没有b属性,这个创建的临时对象就成为包装对象。

类型检测

在JavaScript中,有很多种检测数据的类型,主要是有以下几种

1、typeof:一般用于检测原始数据类型,引用数据类型无法具体的检测出来

console.log(typeof ""); //string
console.log(typeof 1); //number
console.log(typeof true); //boolean
console.log(typeof null); //object
console.log(typeof undefined); //undefined
console.log(typeof []); //object
console.log(typeof function() {}); //function
console.log(typeof {}); //object

其实null是js设计的一个败笔,早期准备更改null的类型为null,由于当时已经有大量网站使用了null,如果更改,将导致很多网站的逻辑出现漏洞问题,就没有更改过来,于是一直遗留到现在。

2、instanceof:检测引用数据类型

console.log("1" instanceof String);  //false
console.log(1 instanceof Number);  //false
console.log(true instanceof Boolean);  //false
console.log([] instanceof Array);  //true
console.log(function() {} instanceof Function);  //true
console.log({} instanceof Object);  //true

可以看到前三个都是以对象字面量创建的基本数据类型,但是却不是所属类的实例,这个就有点怪了。后面三个是引用数据类型,可以得到正确的结果。如果我们通过new关键字去创建基本数据类型,你会发现,这时就会输出true,如下:

console.log(new String("1") instanceof String); //true
console.log(new Number(1) instanceof Number); //true
console.log(new Boolean(true) instanceof Boolean); //true
console.log([] instanceof Array); //true
console.log(function() {} instanceof Function); //true
console.log({} instanceof Object); //true

3、constructor:似乎完全可以应对基本数据类型和引用数据类型,都能检测出数据类型

console.log(("1").constructor === String); //true
console.log((1).constructor === Number); //true
console.log((true).constructor === Boolean); //true
console.log(([]).constructor === Array); //true
console.log((function() {}).constructor === Function); //true
console.log(({}).constructor === Object); //true

事实上并不是如此,来看看为什么:

function Fn(){};
Fn.prototype=new Array();
var f=new Fn();

console.log(f.constructor===Fn);  //false
console.log(f.constructor===Array);  //true

声明了一个构造函数,并且把他的原型指向了Array的原型,所以这种情况下,constructor也显得力不从心了。

4、Object.prototype.toString:终极数据检测方式

var a = Object.prototype.toString;

console.log(a.call("aaa"));  //[object String]
console.log(a.call(1));  //[object Number]
console.log(a.call(true));  //[object Boolean]
console.log(a.call(null));  //[object Null]
console.log(a.call(undefined));  //[object Undefined]
console.log(a.call([]));  //[object Array]
console.log(a.call(function() {}));  //[object Function]
console.log(a.call({}));  //[object Object]

表达式和运算符

表达式

概念
表达式是指能计算出值的任何可用程序单元,或者可以这么说:表达式是一种JavaScript短语,可以使JavaScript解释器用来产生一个值。

原始表达式

  • 常量、直接量:比如3.14、“test”等这些;
  • 关键字:比如null,this,true等这些;
  • 变量:比如i,k,j等这些。

复合表达式
比如:10*20

数组和对象的初始化表达式
比如:[1,2]、{x:1,y:2}等这些。

函数表达式
比如:var fe = function(){}或者(functiong(){console.log('hello world');})()

属性访问表达式
比如:var o = {x:1},访问属性的方式有o.x或者o['x']

调用表达式
比如:funName()

对象创建表达式
比如:new Func(1,2)或者new Object

运算符

太多太基础啦,不多说,稍微提一下以下几个运算符

// 逗号运算符
var n = (1,2,3)  //n=3

// 删除运算符
var obj = {x:1}
obj.x;  //1
delete obj.x;
obj.x;  //undefined

var obj = {};
Object.defineProperty(obj,'x',{
  configurable:false,
  value:1
});
delete obj.x;  //false
obj.x;  //1


// in运算符
window.x = 1;
'x'  in window;  //true

// new运算符
function Foo(){}
Foo.prototype.x = 1;
var obj = new Foo();
obj.x;  // 1
obj.hasOwnProperty('x');  // false
obj.__proto__.hasOwnProperty('x');  // true

// this运算符
this;
var obj = {
    func:function(){return this;}
};
obj.func();  //obj

语句

block语句和var语句

block语句
块语句常用于组合0~N个语句,往往用一对花括号定义。

{
  var str = 'hi';
  console.log(str);
}

if(true) {
  console.log('hi');
}

在ES6出来之前,JavaScript是没有块级作用域的,具体看以下两段代码:

for (var i=0; i<10; i++) {                
  var str = 'hi';  
  console.log(str); 
}  
等同于
 var i = 0;
for (; i<10; i++) {                
  var str = 'hi';  
  console.log(str); 
} 

{
  var x = 1;
}
等同于
var x = 1;
{

}

function foo() {
  var a = 1;
  console.log(a);  //1
}
foo();
console.log(typeof a);  //undefined

声明语句var

function foo() {
  //隐式的将b定义为全局变量
  var a = b = 1;
}
foo();
console.log(typeof a);  //undefined
console.log(typeof b);  //number

function foo() {
  //同时定义多个变量的正确方式
  var a =1,b = 1;
}
foo();
console.log(typeof a);  //undefined
console.log(typeof b);  //undefined

try-catch语句

try跟catch搭配使用可以检测try里边的代码有没有抛出error,如果有error就会跳转到catch里执行catch里的程序。执行顺序为:先try捕获异常,执行catch里面的内容,最后执行finally里面的内容,当然也可以只写catch或者finally两个中的一个,但是必须写try语句块。

try {
  throw 'test';
} catch(e) {
  console.log(e); //test
} finally {
  console.log('必須執行的');
}

嵌套使用

try {
  try {
    throw new Error('oops');
  } finally {
    console.log('finally');
  }
} catch(e) {
  console.error('outer', e.message);
}

由于在嵌套层中并没有catch语句,因此输出结果的顺序为:finally、outer、oops。下面请看含有catch语句的

try {
  try {
    throw new Error('oops');
  } catch(ex) {
    console.log('inner', ex.message);
  } finally {
    console.log('finally');
  }
} catch(ex) {
  console.error('outer', ex.message);
}

以上代码输出结果的顺序为:inner、oops、finally。原因是异常已经在内部处理过了,因此不会再到外部去处理。

更复杂的嵌套

try {
  try {
    throw new Error('oops');
  } catch(ex) {
    console.log('inner', ex.message);
    throw ex;
  } finally {
    console.log('finally');
  }
} catch(ex) {
  console.error('outer', ex.message);
}

以上代码输出结果的顺序为:inner、oops、finally、outer、oops。原因在内部的catch语句重新向外抛出了ex这个异常。

for in语句

有以下的特点:1、顺序不确定;2、enumerable为false时不会出现;3、fon in对象属性时会受到原型链的影响

var p;
var obj = { x: 1, y: 2 }
for(p in obj) {
  console.log(p);  //x  y
}

严格模式

这里有一篇非常好的总结,传送门

对象

概述

对象中包含一系列的属性,这些属性是无序的,每一个属性都有一个字符串key和对应的value

var obj = {x:1,y:2};
obj.x;  //1
obj.y; //2

创建对象

创建对象方式1—字面量:

var obj1 = {x:1,y:2};
var obj2 = {
  x:1,
  y:2,
  o:{
    z:3,
    n:4
  }
};

创建对象方式2—通过构造函数

function foo() {}
foo.prototype.z = 3;
var obj = new foo();
obj.x = 1;
obj.y = 2;
console.log(obj.x); //1
console.log(obj.y); //2
console.log(obj.z); //3
console.log(typeof obj.toString()); //string
console.log(typeof obj.toString);  //function
console.log('z' in obj); //true
console.log(obj.hasOwnProperty('z')); //false

创建对象方式3—Object.create

var obj = Object.create({ x: 1 });
console.log(obj.x);  //1
console.log(obj.toString);  //function
console.log(obj.hasOwnProperty('x'));  //false

// null对象的原型链少了Object这一层
var obj1 = Object.create(null);
console.log(obj1.toString);  //undefined

属性操作

属性读写

var obj = {x:1,y:2};
obj.x;  //1
obj['y'];  //2

//使用[]取得属性值一般场景
var obj = {x1:1,x2:2};
for(var i=1; i<=2; i++){
  console.log(obj['x' + i];
}

属性删除

var person = {age:28,title:'test'};
delete person.age;  //true
delete preson['title'];  //true
person.age;  //undefined
delete person.age;  //true

//无法删除原型
delete Object.prototype;  //false
//因为原型的configurable不可配置
var descriptor = Object.getOwnPropertyDescriptor(Object,'prototype');
descriptor.configurable;  //false

属性检测

var cat = new Object();
cat.legs = 4;
cat.name = 'tom';

//for in检测
'legs' in cat;  //true
'abc' in cat;  //false
'toString' in cat;  //true

//检测对象本身的属性
cat.hasOwnProperty('legs');  //true
cat.hasOwnProperty('toString');  //false

//检测对象的属性是否可枚举,包括原型上的
cat.propertyIsEnumerable('legs');  //true
cat.propertyIsEnumerable('toString');  //false

属性枚举

var o = { x: 1, y: 2, z: 3 };
'toString' in o; //true
o.propertyIsEnumerable('toString'); //false
var key;
for(key in o) {
  console.log(key); //x,y,z
}

var obj = Object.create(o);
obj.a = 4;
var key;
for(key in obj) {
  console.log(key); //a,x,y,z
}

var obj = Object.create(o);
obj.a = 4;
var key;
for(key in obj) {
  if(obj.hasOwnProperty(key)) {
    console.log(key); //a
  }
}

getter()和setter()方法

var man = {
  name: 'tom',
  weibo: 'haha',
  get age() {
    return new Date().getFullYear() - 1988;
  },
  set age(val) {
    console.log('你没有权限设置年龄为:' + val);
  }
}
console.log(man.age);  //30
man.age = 100;  //你没有权限设置年龄为:100
console.log(man.age);  //30

//修改一下,使上面的代码更复杂
var man = {
  name: 'tom',
  weibo: 'haha',
  $age: null,
  get age() {
    if(this.$age == undefined) {
      return new Date().getFullYear() - 1988;
    } else {
      return this.$age;
    }
  },
  set age(val) {
    val = +val;
    if(!isNaN(val) && val > 0 && val < 150) {
      this.$age = +val;
    } else {
      console.log('年龄无法设置为:' + val);
    }
  }
}
console.log(man.age); //30
man.age = 100;
console.log(man.age); //100
man.age = 'abc'; //年龄无法设置为:NaN

get/set与原型链

//get和set设置的值不会被直接更改
function foo() {}
Object.defineProperty(foo.prototype, 'z', {
  get: function() {
    return 1;
  }
})
var obj = new foo();
console.log(obj.z); //1
obj.z = 10;
console.log(obj.z); //1

//defineProperty设置的值不会被直接更改
var o = {}
Object.defineProperty(o, 'x', { value: 5 });
var obj = Object.create(o);
console.log(obj.x); //5
obj.x = 200;
console.log(obj.x); //5

//更改defineProperty设置的值
var o = {}
Object.defineProperty(o, 'x', { writable: true, configurable: true, value: 5 });
var obj = Object.create(o);
console.log(obj.x); //5
obj.x = 200;
console.log(obj.x); //200

属性标签

var person = {};
Object.defineProperty(person, 'name', {
  configurable: false,
  writable: false,
  enumerable: true,
  value: 'tom'
})
console.log(person.name);  //tom
person.name = 'jack';
console.log(person.name);  //tom
console.log(delete person.name);  //false
//获得属性的标签值
console.log(Object.getOwnPropertyDescriptor(person,'name'));

//属性标签的应用
var person = {};
Object.defineProperties(person, {
  title: {
    value: 'JavaScript',
    enumerable: true
  },
  corp: {
    value: 'BAT',
    enumerable: true
  },
  salary: {
    value: 50000,
    enumerable: true,
    writable: true
  },
  luck: {
    get: function() {
      return Math.random() > 0.5 ? 'good' : 'bad';
    }
  },
  promote: {
    set: function(level) {
      this.salary *= 1 + level * 0.1;
    }
  }
});
console.log(Object.getOwnPropertyDescriptor(person, 'salary'));
console.log(person.salary); //50000
person.promote = 2;
console.log(person.salary); //60000

image

序列化

//将属性和值序列化为字符串
var obj = { x: 1, y: true, z: [1, 2, 3], nullVal: null };
//{"x":1,"y":true,"z":[1,2,3],"nullVal":null}
console.log(JSON.stringify(obj));

//有点小坑,得注意
var obj1 = { val: undefined, a: NaN, b: Infinity, c: new Date() };
//{"a":null,"b":null,"c":"2018-06-09T10:46:01.929Z"}
console.log(JSON.stringify(obj1));

//把字符串反序列化为属性和值
var obj2 = JSON.parse('{"x":2333}');
//2333
console.log(obj2.x);

数组

数组中的方法

将数组转为字符串

var arr = [1, 2, 3];
console.log(arr.join());  //1,2,3
console.log(arr.join('-'));  //1-2-3

function repeatString(str, n) {
  return new Array(n + 1).join(str);
}
console.log(repeatString('a', 3));  //aaa
console.log(repeatString('hi', 2));  //hihi

将数组逆序输出

var arr = [1, 2, 3];
console.log(arr.reverse());  //3,2,1
console.log(arr);  //3,2,1

将数组进行排序

var arr = [13, 24, 52, 3];
//13,24,3,54
console.log(arr.sort());
//13,24,3,54
console.log(arr);
var arrSort = arr.sort(function(a, b) { return a - b; });
//3,13,24,52
console.log(arrSort);

var arr1 = [
  { age: 25 }, 
  { age: 39 }, 
  { age: 22 }
];
arr1.sort(function(a, b) {
  return a.age - b.age;
})
arr1.forEach(function(item) {
  console.log('age:', item.age);
})

将数组合并

var arr = [1, 2, 3];
//[1,2,3,4,5]
console.log(arr.concat(4, 5));
//[1,2,3]
console.log(arr);
//[1,2,3,10,11,13]
console.log(arr.concat([10, 11], 13));
//[1,2,3,1,[2,3]]
console.log(arr.concat([1, [2, 3]]));

返回部分数组

var arr = [1, 2, 3, 4, 5];
//[2,3]
console.log(arr.slice(1, 3));
//[2,3,4,5]
console.log(arr.slice(1));
//[2,3,4]
console.log(arr.slice(1, -1));
//[2]
console.log(arr.slice(-4, -3));
//[1,2,3,4,5]
console.log(arr);

将数组进行拼接

var arr = [1, 2, 3, 4, 5];
//[3,4,5],从下标为2开始删除
console.log(arr.splice(2));
//[1,2]
console.log(arr);

var arr1 = [1, 2, 3, 4, 5];
//[3,4],从下标为2开始删除,删除两个元素后停止
console.log(arr1.splice(2, 2));
//[1,2,5]
console.log(arr1);

var arr2 = [1, 2, 3, 4, 5];
//[2],从下标为1开始删除,删除一个元素后停止,并且添加a和b这两个元素
console.log(arr2.splice(1, 1, 'a', 'b'));
//[1,"a","b",3,4,5]
console.log(arr2);

将数组进行遍历

var arr = ['a', 'b', 'c', 'd', 'e'];
arr.forEach(function(x, index, a) {
  console.log(x + '-' + index + '-' + (a === arr));
});

将数组进行映射

var arr = [1, 2, 3];
arr.map(function(x) {
  //11,12,13
  console.log(x + 10);
});
//[1,2,3]
console.log(arr);

将数组进行过滤

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.filter(function(x, index) {
  //1,2,3,4,5,6,7,8,9,10
  console.log(x);
  //0,1,2,3,4,5,6,7,8,9
  console.log(index);
  return index % 3 === 0 || x >= 8;
});
//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(arr);

将数组进行判断

var arr = [1, 2, 3, 4, 5];
//判断每一个元素是否小于10
arr.every(function(x) {
  return x < 10;
});
//判断是否有一个元素等于3
arr.some(function(x) {
  return x === 3;
});

将数组元素进行累加操作

var arr = [1, 2, 3];
var sum = arr.reduce(function(x, y) {
  return x + y;
});
//6
console.log(sum);
//[1,2,3]
console.log(arr);

var arr1 = [3, 9, 6];
var max = arr1.reduce(function(x, y) {
  //3-9
  //9-6
  console.log(x + '-' + y);
  return x > y ? x : y;
});
//9
console.log(max);
//[3,9,6]
console.log(arr1);

var arr2 = [3, 9, 6];
var max2 = arr2.reduceRight(function(x, y) {
  //6-9
  //9-3
  console.log(x + '-' + y);
  return x > y ? x : y;
});
//9
console.log(max2);
//[3,9,6]
console.log(arr2);

将数组进行检索

var arr = ['a', 'b', 'c', 'd', 'e', 'a'];
//2,在数组下标为2的位置找到
console.log(arr.indexOf('c'));
//-1,找不到该元素
console.log(arr.indexOf('f'));
//0,在数组下标为0的位置找到,默认是从左到右查找
console.log(arr.indexOf('a'));
//5,在数组下标为5的位置找到,设置是从数组下标为1的位置开始向右查找
console.log(arr.indexOf('a', 1));
//5,在数组下标为5的位置找到,-3表示在d的位置开始向右查找
console.log(arr.indexOf('a', -3));
//0,在数组下标为0的位置找到,-6表示在最左a的位置开始向右查找
console.log(arr.indexOf('a', -6));
//-1,找不到该元素,-3表示在d的位置开始向右查找
console.log(arr.indexOf('b', -3));
//3,在数组下标为3的位置找到
console.log(arr.lastIndexOf('d'));
//-1,找不到该元素,2表示的是在e的位置向右查找
console.log(arr.lastIndexOf('d', 2));
//3,在数组下标为3的位置找到,5表示的是在b的位置向右查找
console.log(arr.lastIndexOf('d', 5));

判断是否为数组

//true
console.log(Array.isArray([]));
//true
console.log(({}).toString.apply([]) === '[object Array]');
//true
console.log([] instanceof Array);
//true
console.log([].constructor === Array);

数组 VS 对象

  • 相同点:都可以继承,数组是对象,对象不一定是数组,都可以当做对象一般添加删除属性
  • 不同点:数组自动更新length,按索引访问数组常常比访问一般对象属性明显迅速,数组对象继承Array.prototype上的大量数组操作方法。

数组 VS 字符串

字符串是一种类数组

var str = 'hello world';
str.charAt(0);  //h
str[1];  //e

Array.prototype.join.call(str,'-');  //h-e-l-l-o--w-o-r-l-d

函数和作用域

函数概念

函数是一块JavaScript代码,被定义一次,但可以执行和调用多次。JavaScript中的函数也是对象,所以函数可以像其他对象那样操作和传递,所以我们也常叫JavaScript中的函数为函数对象。

函数调用方式

  • 直接调用
  • 对象方法调用
  • 构造器调用
  • call/apply/bind调用

函数声音和函数表达式

函数声明

function add(a, b) {
  var x = a;
  var y = b;
  return x + b;
}

函数表达式

//一般表达式
var add = function(a, b) {
	
}
//立即调用表达式
(function() {
	
})();
//返回表达式
return function() {
	
};
//命名函数表达式
var add = function foo(a, b) {
	
}

函数声明和函数表达式的区别
他们最重要的区别就是函数声明能够提前,而函数表达式不行

var num = add(1, 3);
//4
console.log(num);
function add(a, b) {
  var x = a;
  var y = b;
  return x + b;
}

var num1 = add1(1, 3);
//add1 is not a function
console.log(num1);
var add1 = function(a, b) {
  var x = a;
  var y = b;
  return x + b;
}

this的栗子

全局的this(浏览器)

console.log(this.document == document);  //true
console.log(this === window);  //true

this.a = 37;
console.log(window.a);  //37

一般函数中的this

function f1(){
  return this;
}
f1() === window;  //true

function f2(){
'use strict';
return this;
}
f2() === undefined;  //true

作为对象方法中函数的this

var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};
console.log(o.f()); //37

var o = { prop: 37 };
function independent() {
  console.log(this.prop); //37
  return this.prop;
}
o.f = independent;
console.log(o.f()); //37

对象原型链上的this

var o = {
  fn: function() {
    return this.a + this.b;
  }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
//5
console.log(p.fn());

get/set方法与this

function modulus() {
  //abs()返回数的绝对值
  return Math.abs(this.re + this.im);
}
var o = {
  re: 4,
  im: -9,
  get phase() {
    //sqrt()返回数的平方根
    return Math.sqrt(this.re);
  }
};
Object.defineProperty(o, 'modulus', {
  get: modulus,
  enumerable: true,
  configurable: true
});
console.log(o.phase); //2
console.log(o.modulus); //5

构造器中的this

function Abc() {
  this.a = 33;
}
var oo = new Abc();
console.log(oo.a); //33

function Bcd() {
  this.b = 55;
  return { b: 66 };
}
var pp = new Bcd();
console.log(pp.b); //66

call/apply方法与this

function add(c, d) {
  return this.a + this.b + c + d;
}
var o = { a: 1, b: 3 };
//16
console.log(add.call(o, 5, 7));
//34
console.log(add.apply(o, [10, 20]));

function bar() {
  //[object Window]
  console.log(Object.prototype.toString.call(this));
}
bar();
//[object Number]
console.log(bar.call(10));

bind方法与this

function bar() {
  return this.a;
}
var ger = bar.bind({
  a: 'test'
});
//f bar(){ return this.a; }
console.log(ger);
//test
console.log(ger());

var odr = {
  a: 33,
  bn: bar,
  gn: ger
};
//33
console.log(odr.bn());
//test
console.log(odr.gn());

函数属性arguments

function foo(x, y, z) {
  //2
  console.log(arguments.length);
  //3
  console.log(arguments[0]);
  arguments[0] = 100;
  //100
  console.log(x)
  arguments[2] = 200;
  //undefined
  console.log(z);
  //200
  console.log(arguments[2]);
  //true
  console.log(arguments.callee === foo);
}
foo(3, 4);
//3
console.log(foo.length);
//foo
console.log(foo.name);

apply/call方法(浏览器)

function foo(x, y) {
  console.log(x, y, this);
}
//1,2,Number(100)
foo.call(100, 1, 2);
//3,4,Boolean(true)
foo.apply(true, [3, 4]);
//undefined,undefined,window
foo.apply(null);
//undefined,undefined,window
foo.apply(undefined);

function foo(x, y) {
  'use strict';
  console.log(x, y, this);
}
//undefined,undefined,null
foo.apply(null);
//undefined,undefined,undefined
foo.apply(undefined);

bind方法

this.x = 9;
var module = {
  x: 33,
  getX: function() {
    return this.x;
  }
};
console.log(module.getX()); //33

var gn = module.getX;
console.log(gn()); //9

var gn1 = gn.bind(module);
console.log(gn1()); //33

bind与currying
函数有柯里化(currying)的特性,意思函数能拆分成多个单元。

function add(a, b, c) {
  return a + b + c;
}
var fn = add.bind(undefined, 100);
console.log(fn(1, 2)); //103

var fn1 = fn.bind(undefined, 200);
console.log(fn1(10)); //310

柯里化的实际运用

function getConfig(colors, size, otherOptions) {
  console.log(colors, size, otherOptions);
}
var defaultConfig = getConfig.bind(null, '#cc0000', '1024*768');
//#cc0000 1024*768 123
console.log(defaultConfig('123'));
//#cc0000 1024*768 456
console.log(defaultConfig('456'));

bind与new

function foo() {
  this.b = 100;
  return this.a;
}
var fn = foo.bind({ a: 1 });
//1
console.log(fn());
//{b:100}
console.log(new fn());

bind方法模拟

if(!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if(typeof this !== 'function') {
      throw new TypeError('what is trying to be bound is not callable');
    }
    var aArgs = Array.prototype.slice.call(arguments, 1);
    var fToBind = this;
    var fNOP = function() {};
    var fBound = function() {
      return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
    };
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
  };
}

以上代码有做了些修改,原始代码以及分析请看下图:
image

理解闭包

闭包的栗子

function outer() {
  var localVal = 30;
  return localVal;
}
console.log(outer());

function outer1() {
  var localVal2 = 33;
  return function() {
    return localVal2;
  }
}
var fn = outer1();
console.log(fn())

闭包概念
在计算机科学中,闭包(也成为词法闭包或者函数闭包)是指一个函数或者函数的引用,与一个引用环境绑定在一起,这个引用环境是一个存储该函数每个非局部变量(也叫自由变量)的表。

闭包,不同于一般的函数,它允许一个函数在立即词法作用域外调用时,仍可访问非本地变量。

闭包特性

  • 优点:灵活、方便、封装;
  • 缺点:空间浪费、内存泄露、性能消耗。

作用域

全局作用域、函数作用域和eval

//全局变量
var a = 10;
(function() {
  //局部变量
  var b = 20;
})();
//10
console.log(a);
//undefined
console.log(b);

for(var item in { a: 1, b: 2 }) {
  //a  b
  console.log(item);
}
//b
console.log(item);

eval('var c = 100');
//100
console.log(c);

作用域链

function outer1() {
  var local1 = 11;
  function inter() {
    var local2 = 22;
    //11
    console.log(local1);
    //22
    console.log(local2);
    //33
    console.log(global1);
  }
  inter();
}
var global1 = 33;
outer1();

function outer2() {
  var local3 = 44;
  var fn = new Function('console.log(typeof local3);');
  //undefined
  fn();
}
outer2();

执行上下文

这个玩意太抽象了,看完视频对这个概念还是十分模糊,还是来这看一下比较容易理解的文章——王福朋深入理解JavaScript系列

OOP知识点

概念与继承

面向对象程序设计是一种程序设计规范,同时也是一种程序开发的方法。对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。

看了课程的相关知识点,发现远远没有下面几篇博文说的这么简单易懂与透彻,在这就不整理课程的内容了,一起来看看以下三个博客中内容:
冴羽博客JavaScript系列
汤姆大叔深入理解JavaScript系列
王福朋深入理解JavaScript系列

JavaScript正则表达式

这节课的内容也不摘抄了,因为之前做过这方面的总结,再继续总结一次我就认为是得不偿失了。
JavaScript正则表达式

参考资料

1、博客园小火柴的前端学习之路
2、冴羽博客JavaScript系列
3、汤姆大叔深入理解JavaScript系列
4、JavaScript包装对象
5、JavaScript检测数据类型四种方法
6、JavaScript中的new到底做了些什么
7、王福朋深入理解JavaScript系列
8、JavaScript正则表达式

移动web开发适配秘籍Rem

前言

该文章通过慕课网教程《移动web开发适配秘籍Rem》编写而成,大体上的内容与课程一致。

移动端开发有如下的特点

  • 跑在手机端的web页面(H5页面);
  • 跨平台;
  • 基于webview;
  • 告别IE拥抱webkit;
  • 更高的适配和性能要求。

常见移动web适配方法

(1)PC端

  • 960px/1000px居中;
  • 盒子模型,定宽,定高;
  • display:inline-block。

(2)移动web

  • 定高,宽度百分比;
  • flex;
  • Media Query(俗称媒体查询,和flex组合被称为响应式布局)。

Media Query详解

(1)基本语法
@media 媒体类型 and (媒体特性){
/css样式/
}
媒体类型:screen,print。。。
媒体特性:maxwidth,max-height,min-width,min-height。。。
(2)基本案例

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>MediaQuery</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .box {
        width: 100%;
        background-color: red;
      }
      .inner:nth-child(1) {
        background-color: red;
      }
      .inner:nth-child(2) {
        background-color: blue;
      }
      .inner:nth-child(3) {
        background-color: yellow;
      }
      .inner:nth-child(4) {
        background-color: green;
      }
      @media screen and (max-width: 320px) {
        .inner {
          height: 100px;
          width: 25%;
          float: left;
        }
      }
      @media screen and (min-width: 321px) {
        .inner {
          height: 100px;
          width: 100%;
        }
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="inner"></div>
      <div class="inner"></div>
      <div class="inner"></div>
      <div class="inner"></div>
    </div>
  </body>
</html>

rem原理与简介

(1)rem是一种字体单位,值根据html根元素大小而定,同样可以作为宽度,高度等单位;
(2)适配原理是将px替换成rem,动态修改html的font-size适配;
(3)兼容IOS6以上和Android2.1以上,基本覆盖所有流行的手机系统。
(4)rem原理代码分析

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
    <title>Rem</title>
    <style>
      html {
        font-size: 17px;
      }
      .box {
        height: 10rem;
        width: 10rem;
        background-color: red;
      }
      .text {
        color: #fff;
        font-size: 16px;
      }
      /*
       1rem = 17px = html的font-size(默认为16px)
       10rem = 170px
       * */
    </style>
  </head>
  <body>
    <div class="box">
      <p class="text">hello rem</p>
    </div>
  </body>
</html>

动态修改HTML中fontsize

方式一:使用media

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
    <style>
      html, body {
        margin: 0;
        padding: 0;
      }
      div {
        height: 4rem;
        width: 100%;
        background-color: black;
        font-size: 1rem;
        color: white;
        text-align: center;
      }
      @media only screen and (min-width: 360px) and (max-width: 860px) {
        html {
          font-size: 300px;
        }
        div {
          color: red;
        }
      }
      @media only screen and (min-width: 860px) {
        html {
          font-size: 200px;
        }
        div {
          color: green;
        }
      }
    </style>
  </head>
  <body>
    <div>hello rem</div>
  </body>
</html>

方式二:采用JavaScript的方式

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
    <style>
      html, body {
        margin: 0;
        padding: 0;
      }
      div {
        height: 4rem;
        width: 100%;
        background-color: black;
        font-size: 1rem;
        color: white;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div>hello rem</div>
    <script>
      // 获取视窗的宽度
      let htmlWidth = document.documentElement.clientWidth || document.body.clientWidth
      console.log(htmlWidth)
      // 获取视窗对象
      let htmlDom = document.getElementsByTagName('html')[0]
      console.log(htmlDom)
      // 设置html的font-size
      htmlDom.style.fontSize = htmlWidth / 10 + 'px'
    </script>
  </body>
</html>

rem进阶

(1)rem基准值的计算:1rem = html的font-size
(2)rem数值计算与构建:
(3)rem与scss结合使用:

@function px2rem($px) {
  $rem: 37.5px;
  @return ($px/$rem) + rem;
}
.demo {
  width: px2rem(75px);
  height: px2rem(37.5px);
}

// 编译后的结果
.demo {
  width: 2rem;
  heigth: 1rem;
}

(4)rem适配实战:采用rem高仿网易新闻H5版新闻列表页

  • 步骤一:首先得安装好node和webpack,相关教程请上网自行百度,安装成功的结果是输入node -vnpm -vwebpack -v能够出现相应的版本号;
  • 步骤二:命令行进入项目的目录,然后执行npm init,在项目中创建一个package.json文件;
  • 步骤三:将课程中package.json文件里面中dependciess这部分代码copy进去;
"dependencies": {
    "css-loader": "^0.28.9",
    "node-sass": "^4.7.2",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.20.2",
    "url-loader": "^0.6.2"
}

注意: 由于我是全局安装webpack,因此在dependencies中少了一处配置。

  • 步骤四:运行命令npm install
  • 步骤五:创建一个webpack.config.js文件,并进行配置,参考配置
  • 步骤六:跟着课程一点一点敲代码
  • 最终课程效果

项目实战适配的原理分析

首先使用css预编译语言写好样式代码

@function px2rem($px) {
  // iphone6的宽度除以10
  $rem: 37.5px;
  @return ($px / $rem)+rem;
}

html {
  background-color: #f8f8f8;
}

.header {
  height: px2rem(40px);
  width: 100%;
  background-color: #6170b1;
  padding-left: px2rem(32px);
  box-sizing: border-box;
  .header-item {
    list-style-type: none;
    float: left;
    color: #D1DFB7;
    font-size: px2rem(16px);
    margin-right: px2rem(20px);
    line-height: px2rem(40px);
    &:nth-child(2) {
      color: #fff;
      font-size: px2rem(17px);
    }
  }
}

接着将该样式require进一个js文件中,动态更改html的font-size

require("./index.scss");
// 获得屏幕的宽度
let htmlWidth = document.documentElement.clientWidth || document.body.clientWidth;
// 获得html的dom
let htmlDOM = document.getElementsByTagName('html')[0];
// 设置html的fontsize
htmlDOM.style.fontSize = htmlWidth / 10 + 'px';

可能需要用到的其他知识

移动端H5解惑—页面适配
Sass初入门
webpack和node简单安装使用

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.