ZhangYang's Blog

JavaScript基础

JavaScript基础概念

标识符

  • 指变量、函数、属性的名字,或者函数的参数

标识符的书写有几个特征

  • 区分大小写
  • 第一个字符必须是字母、下划线(_)、或者是$
  • 后面的可以是字母、数字、下划线、$

命名规约

  • 使用是实际意义的单词
  • 变量使用驼峰规则,第一个单词首字母小写,后面单词首字母大写
  • 变量使用名词,方法函数使用动词开头,常量全部用大写字母,函数创建对象首字母大写
1
2
3
4
5
6
7
var firstSecond;
var myCar;
var hasId;
var PI;
var MAX_COUNT;
function getAge(){}
function Person(){}

变量

  • JavaScript中变量是用来保存值的占位符,定义变量的时候要使用var运算符,后面跟一个作为名称的标识符即可
1
var message;

弱类型

  • 在一些编译语言(C、Java、C#)等变量的类型是固定的,在声明变量的时候就要标识其类型,在程序执行前编译阶段变量的类型就确定了,而且不能改变,称之为强类型
1
2
int a = 2;
string b = "hello";
  • 解释型语言(PHP、JavaScript)等变量的类型是松散的,一个变量可以用来保存任何类型的数据,变量的类型不是由声明时决定(声明的时候只是用了var运算符),而是在程序执行阶段由其值的类型决定,随着程序运行其值类型改变,变量类型也会发生改变
1
2
var message = 1; //message 类型就是数字
message = "hello world!"; //message 类型变为字符串

语句

  • 语句(statement)是为了完成某种任务而进行的操作,比如下面就是一行赋值语句
1
var a = 1 + 2;
  • 这条语句先用var运算符,声明了变量a,然后将 1+2 的运算结果赋值给变量a
  • JavaScript中语句以;结束,一行可以包含多条语句,如果省略分号不会产生语法错误,解释器会自动判断语句结束
1
2
var sum = 1 + 2
var diff = 3 - 4;

表达式

  • 一个为了得到返回值的计算式(凡是JavaScript语言中预期为值的地方,都可以使用表达式)
1
1+3
  • 语句和表达式的区别在于,前者主要为了进行某种操作,一般情况下不需要返回值;后者则是为了得到返回值,一定会返回一个值

表达式的几种形式

原始表达式

  • 常量、变量、保留字

对象、数组初始化表达式

  • var obj = {a:1,b:2};
  • var a =[1,2,3];

函数定义表达式

  • var fn = function(){}

属性访问表达式

  • Math.abs

调用表达式

  • alert(‘hello’)

对象创建表达式

  • new object()
  • 变量名也是表达式,因为计算出的结果就是赋值给变量的值

变量提升

  • JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升
1
var a = 2;
  • 实际上执行过程是解释器在未执行的时候先解析出变量声明,然后给他初始值undefined,然后才逐句执行程序
1
2
var a;
a = 2;

注释

  • 通过注释功能让js引擎忽略部分语句,用来解释我们的部分语句
  • 两种注释:一种是单行注释,用//起头;另一种是多行注释,放在//之间
1
2
3
4
5
6
7
8
9
10
11
12
//为变量赋值
var a = 1; //赋值成功
/*
下面定义个函数
至于什么是函数
且听下回分解
*/
function getName(id){
return 'Byron';
}

关键字和保留字

  • 关键字是JavaScript引擎会用到的一些字,我们标识符不能再使用
  • break\case\catch\continue\default\delete\do\else\finally\for\function\if\in\instanceof\new\return\switch\this\throw\try\typeof\var\void\while\with
  • js还规定了一些不能用作标识符的保留字,这些字符没有什么意义,但是未来会用到
  • abstract\boolean\byte\char\class\const\debugger\double\enum\export\extends\final\float\goto\implements\import\int\interface\long\native\package\private\protected\public\short\static\super\synchronized\throws\transient\volatile

JavaScript语法

CSS和JS放置顺序

  • 浏览器渲染机制一种是等全部加载后再一起渲染(IE、Chrome),另一种是边加载边渲染(Firefox)
  • 常用方法:根据不同浏览器渲染机制不同一般选用折中的方法来解决即CSS一般用style标签放在的末尾,而JS放置在末尾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>标题</title>
<link href="index.css" rel="stylesheet">
<style>
body{
background: red;
}
</style>
</head>
<body>
<p>
</p>
<script src="index.js"></script>
<script>
alert(1);
</script>
</body>
</html>
  • CSS放置在head,浏览器会先读取CSS的样式,当读到里面的内容时候,读一个添加一个样式;如果CSS放在后面,对于IE、Chorme浏览器,可能出现白屏问题;对于Firefox浏览器,可能会出现无样式内容闪烁
  • JS放置在后面,JS加载的过程中,其它HTML、CSS等读取会停下来,等待JS加载完后在读取后面的内容,JS阻塞了后面内容的呈现以及后面组件的加载;故采取HTML和CSS先静态页面出来,再读取JS,让页面动起来

白屏和FOUC

白屏

  • 网页无任何内容样式呈白色背景的现象

出现白屏的情况

  • 对于IE、Chrome,CSS样式放在底部,页面出现白屏而不是内容逐步展现
  • @import标签,即使CSS放入link,并且放在头部也会出现白屏(直接link放在顶部代替@import)
  • 对于CSS加载可以并发加载,而加载JS是会禁用并发,所以JS放在顶部也会导致白屏

FOUC(无样式内容闪烁)

  • 页面只有HTML结构无样式的现象

出现FOUC的情况

  • 对于Firefox,CSS样式放在底部,浏览器逐步呈现无样式的内容,所以用户看到的页面就是无样式内容闪烁
  • 白屏和FOUC出现的原因是因为两种浏览器的渲染机制不同造成的,所以解决办法就是将CSS放在顶部,这样不管浏览器渲染机制如何,最先得到CSS样式一般就不会出现这两种情况

async和defer的作用

  • 默认情况下,读到script标签会立即加载并执行脚本,阻塞后面的内容
1
<script src="index.js"></script>

async

  • js加上async后,这条js不会阻塞后面的内容,两者并行执行,并且这条js加载完成后会马上执行
1
<script async src="index.js"></script>

defer

  • js加上defer后,这条js不会阻塞后面的内容,两者并行执行,但这条js是被加载,执行要等到所有元素解析完成之后
1
<script defer src="index.js"></script>

区别

  • async不保证js的执行顺序,谁先加载完就谁先执行
  • defer保证了js的执行顺序,加载完了都不许执行,等所有元素解析完了,js再一条一条执行

网页的渲染机制

  • 浏览器读取HTML,构建DOM树
  • 浏览器读取CSS,构建CSSOM树
  • 浏览器将DOM树和CSSOM树,组合成渲染树 (render tree)
  • 在渲染树的基础上进行布局,计算每个节点的几何结构
  • 浏览器把每个节点绘制到屏幕上

js-1.png

不同的浏览器,呈现机制不一样

IE和chrome

  • 浏览器会把所有的HTML内容都添加上CSS样式后,再呈现出来,内容过多样式加载过慢会出现白屏问题

Firefox

  • 浏览器会渲染一句呈现一句
  • 如果CSS放在头部,就会页面一点一点呈现
  • 如果CSS放在尾部,就会出现无样式内容闪烁

数据类型(5+1种)

  • 数值(number):整数和小数(比如1和3.14)
  • 字符串(string):字符组成的文本(比如”Hello World”)
  • 布尔值(boolean):true(真)和false(假)两个特定值
  • undefined:表示“未定义”或不存在,即此处目前没有任何值
  • null:表示空缺,即此处应该有一个值,但目前为空
  • 对象(object):各种值组成的集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var num = 100;
var str = 'nihao';
var isOk = true ;
var hello; //undefined
var empty = null;
var person = {
name: 'xiaoming',
age: 26
}
var arr = [1, 2, 3];
var sayName = function(){
console.log('my name is xiaoming')
}
var reg = /hello/;
  • 数值、字符串、布尔值称为原始类型的值,最小单元;对象称为复杂类型的值,是多个原始类型的集合
  • undefined和null是两个特殊值
  • 对象分狭义的对象(object)、数组(array)、函数(function)等等

Boolean

  • 布尔值代表“真”和“假”两个状态。“真”用关键字true表示,“假”用关键字false表示。布尔值只有这两个值

下列运算符会返回布尔值

  • 两元逻辑运算符: && (And),|| (Or)
  • 前置逻辑运算符: ! (Not)
  • 相等运算符:===,!==,==,!=
  • 比较运算符:>,>=,<,<=

如果JavaScript预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false,其他值都视为true

  • undefined
  • null
  • false
  • 0
  • NaN
  • “”(空字符串)
1
2
3
4
if ('') {
console.log(true);
}
// 由于空字符串是false,所以没有任何输出

Number

  • JavaScript的数字类型和其它语言有所不同,没有整型和浮点数的区别,统一都是Number类型,可以表示十进制、八进制、十六进制
1
2
3
var a = 10; //十进制
var b = 073; //八进制
vat c = 0xf3; //十六进制

浮点数

  • 浮点数是指数字包含小数点,小数点后至少有一位数字(没有或者是0会转为整数),前面可以没有
1
2
var a = 0.27;
var b = .45;
  • 对于极大或极小的数字可以使用科学计数法
1
var a = 3.1e5; //310000
  • 浮点数最高精度是17位,但是在计算的时候精度不如整数
1
2
1 - 0.9; // 0.09999999999999998
0.1 + 0.2; //0.30000000000000004
  • Infinity:表示无穷大
1
1/0 //Infinity

String

  • String是Unicode字符组成的序列,俗称字符串,可以用双引号或者单引号表示,没有区别,匹配即可
1
2
3
var str = 'hello';
var str2 = "baidu";
var str3 = 'hello "xiaoming" ';

Object

  • 对象,就是一种无序的数据集合,由若干个“键值对”(key-value)构成。key我们称为对象的属性,value可以是任何JavaScript类型,甚至可以是对象
1
2
3
4
var obj = {
name: 'xiaoming',
age: 2
};

对象属性的读取方式

1
2
- obj.name;
- obj['name'];
  • 对象的属性key不确定而是一个变量的时候必须使用[]
  • []里可以是任何字符串,而.不能
  • 使用.属性可以不加引号,使用[]属性当是常量的时候必须加引号

NaN、undefined、null

NaN

  • NaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会报错了),即出现在将字符串解析成数字出错的场合
  • NaN和任何值不相等,包括本身
  • 任何涉及NaN的操作都会返回NaN
  • NaN的数据类型是Number类型
  • isNaN()函数,确定是否为NaN
1
2
3
4
5
6
5-'x'; //NaN
alert(NaN == NaN); //false
NaN/10; //NaN
alert(isNaN(10)); //false(10是一个数值)
alert(isNaN("10")); //false("10"可以转换为数值10)
alert(isNaN("blue")); //true(不能被转换为数值)

undefined

  • 表示不存在值,就是此处目前不存在任何值,典型用法如下
  • 变量被声明了,但没有赋值时,就等于undefined。
  • 调用函数时,应该提供的参数没有提供,该参数等于undefined。
  • 对象没有赋值的属性,该属性的值为undefined。
  • 函数没有返回值时,默认返回undefined
1
2
3
4
5
6
7
8
9
10
11
var i;
i // undefined
function f(x){console.log(x)}
f() // undefined
var o = new Object();
o.p // undefined
var x = f();
x // undefined

null

  • 表示空指针,即该处的值现在为空对象,典型用法如下
  • 作为函数的参数,表示该函数的参数是一个没有任何内容的对象
  • 作为对象原型链的终点
1
var a = null;//表示接收一个空对象

typeof和instanceof

  • JavaScript有三种方法,可以确定一个值到底是什么类型,如下
  • typeof运算符
  • instanceof运算符
  • Object.prototype.toString方法

typeof

  • typeof运算符可以返回一个值的数据类型

原始类型

  • 数值、字符串、布尔值分别返回number、string、boolean
1
2
3
typeof 123;// "number"
typeof '123';// "string"
typeof false;// "boolean"

函数

  • 函数返回function
1
2
function f() {};
typeof f;// "function"

undefined

  • undefined返回undefined
  • typeof可以用来检查一个没有声明的变量,而不报错
1
2
3
4
5
6
7
8
9
typeof undefined;// "undefined"
v // ReferenceError: v is not defined
typeof v // "undefined"
// 错误的写法
if (v) { } // ReferenceError: v is not defined
// 正确的写法
if (typeof v === "undefined") { }

object

  • 除此以外,其他情况都返回object
1
2
3
4
typeof window; // "object"
typeof {}; // "object"
typeof [];// "object"
typeof null; // "object"
  • 空数值[]返回值也是object,说明数组本质上也是一种特殊的对象
  • 历史原因造成null返回object,null本质上是一种类似undefined的特殊值

instanceof

  • instanceof 是判断变量是否为某个对象的实例,返回值为true或false
1
2
3
4
5
var o = {};
var a = [];
o instanceof Array; // false
a instanceof Array; // true
a instanceof Object; // true

如何判断一个变量是否是数字、字符串、布尔、函数

  • typeof 运算符可以判断一个变量是否是数字、字符串、布尔、函数

数字

1
2
var a=1;
typeof a; //"number"

字符串

1
2
var b='abc';
typeof b; //"string"

布尔

1
2
var c=false;
typeof c; //"boolean"

函数

1
2
var d=function(){};
typeof d; //"function"

NaN

  • NaN是JavaScript的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合
1
5 - 'x';// NaN
  • 上面代码运行时,会自动将字符串x转为数值,但是由于x不是数值,所以最后得到结果为·NaN·,表示它是“非数字”(NaN)
  • 一些数学函数的运算结果会出现NaN
1
2
3
4
Math.acos(2) // NaN
Math.log(-1) // NaN
Math.sqrt(-1) // NaN
0/0 //NaN
  • 这些计算在数学上本来就是错误的,所以计算结果返回NaN

判断NaN 的方法

  • isNaN() ,判断一个数是否为NaN,返回 true 或false,但是只对数值有效,不是数值的参数会先转化成数值,当转化不了的时候就转成了NaN,所有这个方法判断不一定准确
  • 判断NaN更可靠的方法是,利用NaN是JavaScript之中唯一不等于自身的值这个特点,进行判断
1
2
3
function myIsNaN(value) {
return value !== value;
}

非数值转化为数值

  • 有三个函数可以把非数值转化为数值
  • Number () 把给定的值转换成数字
  • parseInt () 把值转换成整数
  • parseFloat () 把值转换成浮点数

Number ()

  • 如果是Boolean值,true 返回 1,false 返回 0
1
2
Number(true) ;//1
Number(false) ;//0
  • 如果是数字,就是原样
1
Number(3.1415) ;//3.1415
  • 如果是null,返回 0
1
Number(null) //0
  • 如果是undefined,返回NaN
1
Number(undefined) //NaN
  • 如果是字符串,有以下规则
    • 如果是字符串中只包含数字(包括前面带正号或负号的情况),则将其转化成十进制数值
1
2
3
4
Number ('123') //123
Number ('+123') //123
Number ('-123') //-123
Number ('0110') //110 前面的0会忽略
    • 如果字符串中包含的有效的浮点数,就会转化成对应的浮点数,前面的0会忽略
1
2
Number ('3.1415') //3.1415
Number ('03.1415') //3.1415
    • 如果字符串中包含有效的十六进制格式,前面为0x的格式,会自动转化成相同大小的十进制数
1
Number ('0x11') //17
    • 如果字符串是空字符串,则返回 0
1
Number ('') //0
    • 如果字符串中包含上述格式外的其他字符,则转化成NaN
1
2
3
var x
Number (x) //NaN
// 如果 x 没有用 var 声明过,就会报错。

parseInt ()

  • parseInt方法用于将 字符串 转为整数。返回值只有两种可能,不是一个十进制整数,就是NaN
1
2
3
4
parseInt ('520') //520 整数转化成整数
parseInt ('3.1415') //3 浮点数转化前面的整数部分
parseInt (' 11') //11 前面的空格会忽略
parseInt ('000011') //11 前面的0会忽略
  • 如果parseInt的参数不是字符串,则会先转为字符串再转换
1
2
3
parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1
  • 字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分
1
2
3
4
5
6
parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15
//parseInt的参数都是字符串,结果只返回字符串头部可以转为数字的部分。
  • 如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN
1
2
3
4
5
parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') // NaN
parseInt('+1') // 1
  • parseInt方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。默认情况下,parseInt的第二个参数为10,即默认是十进制转十进制
1
2
3
4
5
6
7
8
9
parseInt('1000', 10) // 1000 以十进制解读(默认)
parseInt('1000', 2) // 8 以二进制解读
parseInt('1000', 6) // 216 以六进制解读
parseInt('1000', 8) // 512 以八进制解读
parseInt('10', 37) // NaN 进制超出范围,就返回 NaN
parseInt('10', 1) // NaN 进制超出范围,就返回 NaN
parseInt('10', 0) // 10
parseInt('10', null) // 10
parseInt('10', undefined) // 10 第二个参数是0、null、undefined 则直接忽略

parseFloat ()

  • parseFloat用于将一个字符串转为浮点数
1
2
3
4
5
6
parseFloat('3.14') // 3.14 浮点数转浮点数
parseFloat('314e-2') // 3.14
parseFloat('0.0314E+2') // 3.14 科学计数法转换
parseFloat ('3.14abc') // 3.14 转换前面的数值部分
parseFloat (' 3.14') // 3.14
parseFloat ('00003.14') // 3.14 前面的 0 和空格忽略
  • 如果第一个字符不能转化成浮点数,就返回NaN
1
2
3
parseFloat([]) // NaN 空数组返回 NaN
parseFloat('FF2') // NaN 第一个字符不能转化浮点数
parseFloat('') // NaN 空字符串转化为 NaN

parseFloat () 和 Number () 的区别

  • 一般Number()比较复杂,所以建议使用parseFloat ()
1
2
3
4
5
6
7
8
9
10
11
parseFloat(true) // NaN
Number(true) // 1
parseFloat(null) // NaN
Number(null) // 0
parseFloat('') // NaN
Number('') // 0
parseFloat('123.45#') // 123.45
Number('123.45#') // NaN

JS中=、==、===的区别

  • JS中的=、==、===是不同的

“=”表示赋值

  • 把后面的值赋给前面
1
var a=1 // 赋值

“==”相等运算符

  • 宽松的比较两个数据,如果两个数据类型相同,就直接比较,如果两个数据类型不同,则会先转化数据类型相同,再比较

    • 原始类型间的比较
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 原始类型的数据会转换成数值类型再比较
1 == true // true
// 等同于 1 === 1
0 == false // true
// 等同于 0 === 0
2 == true // false
// 等同于 2 === 1
2 == false // false
// 等同于 2 === 0
'true' == true // false
// 等同于 Number('true') === Number(true)
// 等同于 NaN === 1
'' == 0 // true
// 等同于 Number('') === 0
// 等同于 0 === 0
'' == false // true
// 等同于 Number('') === Number(false)
// 等同于 0 === 0
'1' == true // true
// 等同于 Number('1') === Number(true)
// 等同于 1 === 1
'\n 123 \t' == 123 // true
// 因为字符串转为数字时,省略前置和后置的空格
    • 对象与原始类型比较
1
2
3
4
5
6
7
8
9
10
// 对象与原始类型的值比较时,对象转化成原始类型的值,再进行比较。
[1] == 1 // true
// 等同于 Number([1]) == 1
[1] == '1' // true
// 等同于 String([1]) == Number('1')
[1] == true // true
// 等同于 Number([1]) == Number(true)
    • undefined 和 null
1
2
3
4
5
6
7
8
9
false == null // false
false == undefined // false
0 == null // false
0 == undefined // false
// undefined 和 null与其他类型的值比较时,结果都为false
undefined == null // true
undefinednull 比较时,结果为 true

“===” 严格相等运算符

  • 严格相等运算符(===)比较它们是否为“同一个值”。如果两个值不是同一类型,严格相等运算符(===)直接返回false

    • 不同类型的值
1
2
3
1 === "1" // false
true === "true" // false
// 不同类型的值直接返回 false
    • 同一类的原始类型值
1
2
1 === 0x1 // true
// 十进制和十六进制的 1 ,是相同的值,同一类型
    • 同一类的复合类型值
1
2
3
4
5
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个对象。
{} === {} // false
[] === [] // false
(function (){} === function (){}) // false
// 严格相等运算比较的是,它们是否引用同一个内存地址

break和continue

break

  • break用于强制退出本次循环
1
2
3
4
5
6
7
8
9
10
var num = 0;
for(var i = 1;i < 10;i++){
if(i % 5 == 0){
break;
}
num++;
}
alert(num); //4

continue

  • continue用于退出本次循环,执行下次循环
1
2
3
4
5
6
7
8
9
10
var num = 0;
for(var i = 1;i < 10;i++){
if(i % 5 == 0){
continue;
}
num++;
}
alert(num); //8

void 0 和 undefined在使用场景上有什么区别

  • void 0 运算后返回值是 undefined,不可被重写
  • undefined 在局部作用域中,是可以被重写的,如果要判断一个变量是否和 undefined 相等,可以使用 void 0 来进行比较
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function f(){
var undefined = 100;
var str;
if(str == undefined){
console.log('相等');
}else{
console.log('不相等');
}
}
f(); // 返回不相等,因为undefined 被赋值 100
function f(){
var undefined = 100;
var str;
if(str == void 0){
console.log('相等');
}
}
f(); // 返回相等

code:完成如下代码判断一个变量是否是数字、字符串、布尔、函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function isNumber(el){
// todo...
}
function isString(el){
// todo...
}
function isBoolean(el){
// todo...
}
function isFunction(el){
// todo...
}
var a = 2,
b = "hello world",
c = "false";
alert(isNumber(a)); //true
alert(isString(a)); //false
alert(isString(b)); //true
alert(isBoolean(c)); //true
alert(isFunction(a)); //false
alert(isFunction(isNumber)); //true

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
</body>
<script type="text/javascript">
// 方法一
function isNumber(el){
if(typeof el === "number"){
return true
}else{
return false
}
}
function isString(el){
if(typeof el === "string"){
return true
}else{
return false
}
}
function isBoolean(el){
if(typeof el === "boolean"){
return true
}else{
return false
}
}
function isFunction(el){
if(typeof el === "function"){
return true
}else{
return false
}
}
// 方法二
function isNumber(el){
return typeof el ==="number";
}
function isString(el){
return typeof el ==="string";
}
function isBoolean(el){
return typeof el ==="boolean";
}
function isFunction(el){
return typeof el ==="function";
}
var a = 2,
b = "hello world",
c = false;
alert(isNumber(a)); //true
alert(isString(a)); //false
alert(isString(b)); //true
alert(isBoolean(c)); //true
alert(isFunction(a)); //false
alert(isFunction(isNumber)); //true
</script>
</html>

code:以下代码的输出结果是?

1
2
3
4
5
console.log(1+1);
console.log("2"+"4");
console.log(2+"4");
console.log(+new Date());
console.log(+"4");

explain

1
2
3
4
5
console.log(1+1); //2 加法运算
console.log("2"+"4"); //24 字符串的拼接
console.log(2+"4"); //24 先转换数字为字符串,在拼接
console.log(+new Date()); //1496808358426 获得从1970.1.1开始到当前日期的毫秒数
console.log(+"4"); //4 有运算符,会将字符串转换为数字
  • 运算符通常会对操作数进行类型的转换,称隐式类型转换

code: 以下代码的输出结果是?

1
2
3
var a = 1;
a+++a;
typeof a+2;

explain

1
2
3
var a = 1;
a+++a; //3 等同于(a++)+a,前面(a++)为1,后面a为2,++优先级高于+
typeof a+2; //"number2" 等同于(typeof a)+2,前面为"number",后面为2,typeof优先级高于+

code: 以下代码的输出结果是?

1
2
3
var a = 1;
var b = 3;
console.log(a+++b);

explain

1
2
3
var a = 1;
var b = 3;
console.log(a+++b); //4 括号内等同于(a++)+b,前面的(a++)为1,a++是先用a的值,用完后再给a加1

code:遍历数组,把数组里的打印数组每一项的平方

1
2
3
var arr = [3,4,5];
// todo...
// 输出9,16,25

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 方法一
var arr = [3,4,5];
for(var i=0;i<arr.length;i++){
array=Math.pow(arr[i],2);
console.log(array);
}
// 方法二
var i =0;
while(i<arr.length){
array=Math.pow(arr[i],2);
console.log(array);
i++;
}
//方法三
do{
array=Math.pow(arr[i],2);
console.log(array);
i++;
}while(i<arr.length)

code:遍历JSON,打印里面的值

1
2
3
4
5
6
7
var obj = {
name: "xiaoming",
sex: "male",
age: 28
}
//todo...
//输出name:xiaoming,sex:male,age:28

explain

1
2
3
4
5
6
7
8
9
var obj = {
name: "xiaoming",
sex: "male",
age: 28
}
for(var key in obj){
console.log(key+':'+obj[key]);
}

code:下面代码的输出是什么?

1
2
3
4
console.log(a);
var a =1;
console.log(a);
console.log(b);

explain

1
2
3
4
5
6
7
8
9
10
11
console.log(a);
var a =1;
console.log(a);
console.log(b);
//相当于
var a;
console.log(a); //undefined
a=1;
console.log(a); //1
console.log(b); //报错
  • js存在变量提升机制,使得a声明提升至最前面,此时a没有赋值所以是undefined;到a=1时,给a赋值1,输出1;最后b未声明所以控制台报错

code:如下代码输出什么

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 1, b = 2, c = 3;
var val = typeof a + b || c >0 ;
console.log(val);
var d = 5;
var data = d ==5 && console.log('bb') ;
console.log(data);
var data2 = d = 0 || console.log('haha');
console.log(data2);
var x = !!"Hello" + (!"world", !!"from here!!");
console.log(x) ;

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a = 1, b = 2, c = 3;
var val = typeof a + b || c >0 // 优先级顺序typeof + > ||
console.log(val) // 'number2' || true
// 输出‘number2’
var d = 5;
var data = d ==5 && console.log('bb')
// console.log('bb') 输出了字符串bb,但它的返回值是undefined
console.log(data) // data = true && undefined
//输出 undefined
var data2 = d = 0 || console.log('haha')
// console.log('haha') 输出了字符串haha,但它的返回值是undefined
console.log(data2) // data2 = d = 0 || undefined
//输出 undefined
var x = !!"Hello" + (!"world", !!"from here!!");
// true+(false, true)
console.log(x) // console.log (true+true)
// 输出 2

运算符

  • 运算符是处理数据的基本方法,用来从现有数据得到新的数据

加法运算符

  • 加法运算符(+)是最常见的运算符之一,但是使用规则却相对复杂。因为在JavaScript语言里面,这个运算符可以完成两种运算,既可以处理算术的加法,也可以用作字符串连接,它们都写成+
1
2
3
4
5
6
7
8
// 加法
1 + 1 // 2
true + true // 2
1 + true // 2
// 字符串连接
'1' + '1' // "11"
'1.1' + '1.1' // "1.11.1"
  • 在两个操作数都是数字的时候,会做加法运算
  • 两个参数都是字符串或在有一个参数是字符串的情况下会把另外一个参数转换为字符串做字符串拼接
  • 在参数有对象的情况下会调用其valueOf或toString
  • 在只有一个字符串参数的时候会尝试将其转换为数字
  • 在只有一个数字参数的时候返回其正数值
1
2
3
4
'1' + {foo: 'bar'} // "1[object Object]"
'1' + 1 // "11"
'1' + true // "1true"
'1' + [1] // "11"

算术运算符

  • 加法运算符(Addition):x + y
  • 减法运算符(Subtraction): x - y
  • 乘法运算符(Multiplication): x * y
  • 除法运算符(Division):x / y
  • 余数运算符(Remainder):x % y
  • 自增运算符(Increment):++x 或者 x++
  • 自减运算符(Decrement):–x 或者 x–
  • 数值运算符(Convert to number): +x
  • 负数值运算符(Negate):-x

余数运算符

  • 余数运算符(%)返回前一个运算子被后一个运算子除,所得的余数
1
12 % 5 // 2

自增和自减运算符

  • 自增和自减运算符,是一元运算符,只需要一个运算子。它们的作用是将运算子首先转为数值,然后加上1或者减去1。它们会修改原始变量
1
2
3
4
5
6
var x = 1;
++x // 2
x // 2
--x // 1
x // 1
  • 自增和自减运算符有一个需要注意的地方,就是放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值
1
2
3
4
5
6
var x = 1;
var y = 1;
x++ // 1
++y // 2
上面代码中,x是先返回当前值,然后自增,所以得到1;y是先自增,然后返回新的值,所以得到2

数值运算符

  • 数值运算符(+)同样使用加号,但是加法运算符是二元运算符(需要两个操作数),它是一元运算符(只需要一个操作数)
  • 数值运算符的作用在于可以将任何值转为数值(与Number函数的作用相同)
1
2
3
4
+true // 1
+[] // 0
+{} // NaN
//上面代码表示,非数值类型的值经过数值运算符以后,都变成了数值(最后一行NaN也是数值)

负数值运算符

  • 负数值运算符(-),也同样具有将一个值转为数值的功能,只不过得到的值正负相反。连用两个负数值运算符,等同于数值运算符
1
2
3
var x = 1;
-x // -1
-(-x) // 1

赋值运算符

  • 赋值运算符(Assignment Operators)用于给变量赋值。最常见的赋值运算符,当然就是等号(=),表达式x = y表示将y的值赋给x。除此之外,JavaScript还提供其他11个复合的赋值运算符
1
2
3
4
5
6
7
8
9
10
11
12
x += y // 等同于 x = x + y
x -= y // 等同于 x = x - y
x *= y // 等同于 x = x * y
x /= y // 等同于 x = x / y
x %= y // 等同于 x = x % y
x >>= y // 等同于 x = x >> y
x <<= y // 等同于 x = x << y
x >>>= y // 等同于 x = x >>> y
x &= y // 等同于 x = x & y
x |= y // 等同于 x = x | y
x ^= y // 等同于 x = x ^ y
这些复合的赋值运算符,都是先进行指定运算,然后将得到值返回给左边的变量

比较运算符

  • == 相等
  • === 严格相等
  • != 不相等
  • !== 严格不相等
  • < 小于
  • <= 小于或等于
  • 大于

  • = 大于或等于

  • 比较运算符用于比较两个值,然后返回一个布尔值,表示是否满足比较条件,JavaScript一共提供了8个比较运算符
1
2 > 1 // true

布尔运算符

  • 取反运算符:!
  • 且运算符:&&
  • 或运算符:||
  • 三元运算符:?:

取反运算符(!)

  • 取反运算符形式上是一个感叹号,用于将布尔值变为相反值,即true变成false,false变成true
  • 不管什么类型的值,经过取反运算后,都变成了布尔值
  • 对一个值连续做两次取反运算,等于将其转为对应的布尔值,与Boolean函数的作用相同。这是一种常用的类型转换的写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
!true // false
!false // true
!undefined // true
!null // true
!0 // true
!NaN // true
!"" // true
!54 // false
!'hello' // false
![] // false
!{} // false
!!x
// 等同于
Boolean(x)

且运算符(&&)

  • 第一个操作数为true并且第二个操作数为true,返回true
  • 且运算符的运算规则是:如果第一个运算子的布尔值为true,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值
1
2
3
4
5
6
7
8
9
't' && '' // ""
't' && 'f' // "f"
't' && (1 + 2) // 3
'' && 'f' // ""
'' && '' // ""
var x = 1;
(1 - 1) && ( x += 1) // 0
x // 1
  • 这种跳过第二个运算子的机制,被称为“短路”。有些程序员喜欢用它取代if结构,比如下面是一段if结构的代码,就可以用且运算符改写
1
2
3
4
5
6
7
if (i) {
doSomething();
}
// 等价于
i && doSomething();
  • 且运算符可以多个连用,这时返回第一个布尔值为false的表达式的值
1
2
true && 'foo' && '' && 4 && 'foo' && true
// ''

或运算符(||)

  • 只要一个操作数是true,则返回是true
  • 或运算符(||)的运算规则是:如果第一个运算子的布尔值为true,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false,则返回第二个运算子的值。短路规则对这个运算符也适用
1
2
3
4
't' || '' // "t"
't' || 'f' // "t"
'' || 'f' // "f"
'' || '' // ""
  • 或运算符可以多个连用,这时返回第一个布尔值为true的表达式的值
1
2
false || 0 || '' || 4 || 'foo' || true
// 4
  • 或运算符常用于为一个变量设置默认值
1
2
3
4
5
6
7
8
9
function saveText(text) {
text = text || '';
// ...
}
// 或者写成
saveText(this.text || '')
上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串

三元条件运算符(?:)

  • 三元条件运算符用问号(?)和冒号(:),分隔三个表达式。如果第一个表达式的布尔值为true,则返回第二个表达式的值,否则返回第三个表达式的值
1
2
't' ? 'hello' : 'world' // "hello"
0 ? 'hello' : 'world' // "world"
  • 通常来说,三元条件表达式与if…else语句具有同样表达效果,前者可以表达的,后者也能表达。但是两者具有一个重大差别,if…else是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式,而不能使用if..else
1
2
console.log(true ? 'T' : 'F');
上面代码中,console.log方法的参数必须是一个表达式,这时就只能使用三元条件表达式。如果要用if...else语句,就必须改变整个代码写法了

位运算符

  • 或运算(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1。
  • 与运算(and):符号为&,表示若两个二进制位都为1,则结果为1,否则为0。
  • 否运算(not):符号为~,表示对一个二进制位取反。
  • 异或运算(xor):符号为^,表示若两个二进制位不相同,则结果为1,否则为0。
  • 左移运算(left shift):符号为<<
  • 右移运算(right shift):符号为>>
  • 带符号位的右移运算(zero filled right shift):符号为>>>
  • 这些位运算符直接处理每一个比特位(bit),所以是非常底层的运算,好处是速度极快,缺点是很不直观,许多场合不能使用它们,否则会使代码难以理解和查错
  • 有一点需要特别注意,位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行。另外,虽然在JavaScript内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数

其他运算符

void运算符

  • void运算符的作用是执行一个表达式,然后不返回任何值,或者说返回undefined
1
2
3
var x = 3;
void (x = 5) //undefined
x // 5

逗号运算符

  • 逗号运算符用于对两个表达式求值,并返回后一个表达式的值
1
2
3
4
5
6
'a', 'b' // "b"
var x = 0;
var y = (x++, 10);
x // 1
y // 10

运算顺序

优先级

  • JavaScript各种运算符的优先级别(Operator Precedence)是不一样的。优先级高的运算符先执行,优先级低的运算符后执行
1
4 + 5 * 6 // 34
  • 上面的代码中,乘法运算符(*)的优先性高于加法运算符(+),所以先执行乘法,再执行加法,相当于下面这样
1
2
3
4
var x = 1;
var arr = [];
var y = arr.length <= 0 || arr[0] === undefined ? x : arr[0];
  • 上面代码中,变量y的值就很难看出来,因为这个表达式涉及5个运算符,到底谁的优先级最高,实在不容易记住。
  • 根据语言规格,这五个运算符的优先级从高到低依次为:小于等于(<=)、严格相等(===)、或(||)、三元(?:)、等号(=)。因此上面的表达式,实际的运算顺序如下
有几个我们需要注意的地方
  • typeof的优先级相当的高,比加减乘除神马的都高,所以虽然是操作符,在复杂表达式的时候我们还是习惯加括号
1
2
3
typeof 2*3;//NaN
typeof (2*3);//"number"
typeof 2+3;// "number3"
  • ++、–是右结合的操作符(优先级最高的几个都是右结合),而且比加减乘除优先级高。同时自增、自减运算符的运算数得是左值(可以放在赋值符号左边的值),而不能是常数
1
2
3
4
5
4++; //ReferenceError: Invalid left-hand side expression in postfix operation
var a=0,b=0;
a+++b;//0
a;//1,++优先级比+高,所以相当于(a++)+b
b;//0
  • 赋值运算符的优先级相当的低
1
a = b == c; //等同于a = (b==c)
  • 逻辑非!也在优先级队列的前端,比加减乘除高,但逻辑与、逻辑或优先级很低,不如加减乘除
1
!2*0; //0, 等价于(!2)*0

流程控制语句

条件语句

  • 条件语句提供一种语法构造,只有满足某个条件,才会执行相应的语句

if语句

  • if结构先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句
  • 如果condition为true,就执行紧跟在后面的语句;如果结果为false,则跳过不执行
1
2
3
4
5
6
7
if(condition) {
当条件为 true 时执行的代码
}
if(a > 2){
alert("大于2")
}
  • condition可以是任意表达式,结果不一定是布尔值,JavaScript解释器会自动调用Boolean()将表达式结果转为布尔值
  • 注意,if后面的表达式,不要混淆“赋值表达式”(=)与“严格相等运算符”(===)或“相等运算符”(==)。因为,“赋值表达式”不具有比较作用

if…else结构

  • if代码块后面,还可以跟一个else代码块,表示不满足条件时,所要执行的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(condition) {
当条件为 true 时执行的代码
} else {
当条件不为 true 时执行的代码
}
var a = 100;
if(a > 90){
console.log('优秀');
}else if(a > 60){
console.log('良好');
}else{
console.log('不及格');
}
  • 对同一个变量进行多次判断时,多个if…else语句可以连写在一起
1
2
3
4
5
6
7
8
9
if (m === 0) {
// ...
} else if (m === 1) {
// ...
} else if (m === 2) {
// ...
} else {
// ...
}

switch结构

  • 多个if…else连在一起使用的时候,可以转为使用更方便的switch结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
switch(a){
case 1:
//todo...
break;
case 2:
//todo...
break;
default:
//todo
}
var i = 25;
if(i == 25){
console.log('25');
}else if(i == 35){
console.log('35');
}else if(i == 45){
console.log('45');
}else{
console.log('Other');
}
// 等价于
var i = 25;
switch(i){
case 25:
console.log('25');
break;
case 35:
console.log('35');
break;
case 45:
console.log('45');
break;
default:
console.log('Other');
}
  • 假如没有break语句,导致不会跳出switch结构,而会一直执行下去
  • switch语句在比较值的时候使用全等操作符,不会进行类型转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var score = 70;
if(score >= 90){
console.log('优');
}else if(score >= 70){
console.log('良');
}else if(score >= 60){
console.log('中');
}else{
console.log('差');
}
// 等价于
var score = 70;
switch(true){
case score >= 90:
console.log('优');
break;
case score >= 70:
console.log('良');
break;
default:
console.log('差');
}
  • 这是另一种写法,之所以给表达式传递true,因为每个case值都可以返回一个布尔值

三元运算符 ?:

  • JavaScript还有一个三元运算符(即该运算符需要三个运算子)?:,也可以用于逻辑判断
1
2
3
4
5
6
7
8
9
10
(condition) ? expr1 : expr2
var even;
if (n % 2 === 0) {
even = true;
} else {
even = false;
}
//等同于
var even = (n % 2 === 0) ? true : false;

循环语句

  • 循环语句用于重复执行某个操作,它有多种形式

while循环

  • While语句包括一个循环条件和一段代码块,只要条件为真,就不断循环执行代码块
1
2
3
4
5
6
7
8
9
10
while (expression) {
statement;
}
var i = 0;
while (i < 100) {
console.log('i当前为:' + i);
i += 1;
}

for循环

  • for语句是循环命令的另一种形式,可以指定循环的起点、终点和终止条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for (initialize; test; increment) {
statement
}
var x = 3;
for (var i = 0; i < x; i++) {
console.log(i);
}
// 0
// 1
// 2
//等同于while如下
var x = 3;
var i = 0;
while (i < x) {
console.log(i);
i++;
}
for语句后面的括号里面,有三个表达式
  • 初始化表达式(initialize):确定循环的初始值,只在循环开始时执行一次。
  • 测试表达式(test):检查循环条件,只要为真就进行后续操作。
  • 递增表达式(increment):完成后续操作,然后返回上一步,再一次检查循环条件

do…while循环

  • do…while循环与while循环类似,唯一的区别就是先运行一次循环体,然后判断循环条件
1
2
3
4
5
6
7
8
9
10
do {
statement
} while (expression);
var x = 3;
var i = 0;
do {
console.log(i);
i++;
} while(i < x);

break语句和continue语句

  • break语句和continue语句都具有跳转作用,可以让代码不按既有的顺序执行
break语句
  • break语句用于跳出代码块或循环
1
2
3
4
5
6
7
8
9
10
11
12
for (var i = 0; i < 5; i++) {
console.log(i);
if (i === 3){
break;
}
}
// 0
// 1
// 2
// 3
// 上面代码执行到i等于3,就会跳出循环
continue语句
  • continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环
1
2
3
4
5
6
7
8
9
var i = 0;
while (i < 100){
i++;
if (i%2 === 0) {
continue;
}
console.log('i当前为:' + i);
}
//上面代码只有在i为奇数时,才会输出i的值。如果i为偶数,则直接进入下一轮循环

标签(label)

  • JavaScript语言允许,语句的前面有标签(label),相当于定位符,用于跳转到程序的任意位置
  • 标签可以是任意的标识符,但是不能是保留字,语句部分可以是任意语句
  • 标签通常与break语句和continue语句配合使用,跳出特定的循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
label:
statement
top:
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (i === 1 && j === 1) break top;
console.log('i=' + i + ', j=' + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
top:
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (i === 1 && j === 1) continue top;
console.log('i=' + i + ', j=' + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
// i=2, j=0
// i=2, j=1
// i=2, j=2
  • 不建议使用此语句,看起来很复杂的嵌套

JavaScript函数

  • 函数就是一段可以反复调用的代码块。通过名字来供其它预计调用以执行函数包含的多条代码语句
1
2
3
4
5
6
7
8
9
10
console.log("你好");
alert("你好");
//等同于
function doSomething(){
console.log("你好");
alert("你好");
}
doSomething();

函数声明和函数表达式的区别

函数声明

  • function关键字声明一个函数
1
function fn(){}

函数表达式

  • 用var 声明一个变量,将函数(匿名函数)赋值给这个变量
1
var fn = function(){}
  • 还有一种是构造函数方式,用new来创建一个函数对象,很少用到,不推荐
1
var fn = new Function("我是代码块");

区别

  • 函数声明,声明会提前
  • 函数表达式,函数声明不会提前
1
2
3
4
5
6
7
8
9
10
11
fn();
function fn(){
console.log('hello');
}
//不会报错,因为function声明会自动前置
fn();
var fn = function(){
console.log('hello');
}
//会报错,代码从上到下一步步执行,fn()调用之前并没有声明函数

变量和函数的声明前置

  • JS和C、java等语言不同,JS能够在变量的函数被声明之前使用它们

变量的声明前置

  • 用var创建变量,声明会前置
1
2
3
4
5
6
7
8
console.log(a);
var a=1;
等同于
var a;
console.log(a);
a=1;
//输出undefined,因为变量a的声明前置了,没有赋值就是undefined
  • 一定要var声明的变量才会声明提升
1
2
3
console.log(x);
x=1;
// 报错,因为变量x没有声明

函数的声明前置

  • 用function声明函数,函数声明会前置
1
2
3
4
5
6
7
8
9
10
11
hello();
function hello(){
console.log("你好");
}
//打印"你好",函数声明提升
//等同于
function hello(){
console.log("你好")
}
hello();

arguments

定义

  • 由于JavaScript允许函数有不定数目的参数,所以我们需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来

用法

读取参数

  • arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用
1
2
3
4
5
6
7
8
9
10
var f = function(one) {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
f(1, 2, 3)
// 1
// 2
// 3

参数赋值

  • arguments对象除了可以读取参数,还可以为参数赋值(严格模式不允许这种用法)
1
2
3
4
5
6
7
8
var f = function(a, b) {
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
f(1, 1)
// 5

查询参数个数

  • 可以通过arguments对象的length属性,判断函数调用时到底带几个参数
1
2
3
4
5
6
7
function f() {
return arguments.length;
}
f(1, 2, 3) // 3
f(1) // 1
f() // 0

与数组的关系

  • arguments很像数组,但它是一个对象。数组专有的方法(比如slice和forEach),不能在arguments对象上直接使用
  • 但是,可以通过apply方法,把arguments作为参数传进去,这样就可以让arguments使用数组方法了
1
2
3
4
5
6
7
8
// 用于apply方法
myfunction.apply(obj, arguments) //this指向obj
// 使用与另一个数组合并
Array.prototype.concat.apply([1,2,3], arguments) //this指向[1,2,3]这个数组对象
// 等同于上面
[].concat.apply([1,2,3],arguments)
  • 要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种常用的转换方法:slice方法和逐一填入新数组
1
2
3
4
5
6
7
8
9
10
var args = Array.prototype.slice.call(arguments); //this指向arguments对象
// 等同于上面
[].slice.call(arguments);
// or
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}

callee属性

  • arguments对象带有一个callee属性,返回它所对应的原函数
1
2
3
4
5
var f = function(one) {
console.log(arguments.callee === f);
}
f() // true
  • 可以通过arguments.callee,达到调用函数自身的目的。这个属性在严格模式里面是禁用的,因此不建议使用

函数的重载实现

  • 指同一范围内声明几个同名函数,它们的功能类似,但是形参不同(指参数的个数、类型或者顺序不同)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 其他语言重载范例
int sum(int num1, int num2){
return num1 + num2;
}
float sum(float num1, float num2){
return num1 + num2;
}
sum(1, 2);
sum(1.5, 2.4);
// 函数会根据形参的类型,这里是整型和浮点型,来选择对应的函数,这就是函数的“重载”
  • JavaScript 中,没有重载,同名函数会覆盖;但可以在函数体针对不同的参数调用执行相应的逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 用其他方法达到重载的效果
function printPeopleInfo(name, age, sex){
if(name){
console.log(name);
}
if(age){
console.log(age);
}
if(sex){
console.log(sex);
}
}
printPeopleInfo('Byron', 26);
printPeopleInfo('Byron', 26, 'male');

立即执行函数表达式(IIFE)

  • JavaScript中,声明一个函数,要想运行它,就要调用它,这是函数的使用方式;如果想要声明这个函数并且立即运行它,就可以把这个函数声明变成表达式,后面加上()就可以立即执行这个函数
1
2
3
4
5
6
7
8
9
10
11
//普通的函数的使用方式
function say(){
console.log('hello world')
} //只是声明,里面的代码不会运行
say();//调用这个函数,输出"hello world"的字符串
// 立即执行函数
(function say(){
console.log('hello world')
})() //第一个()包括函数,把函数声明变成表达式,第二个尾部的(),调用改函数
  • 立即执行函数的作用是:不必为函数命名,避免了污染全局变量;IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量

递归实现n!

  • 自己调用自己
  • 设定终止条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 求 n 的阶乘 n!
function factor(n){
if(n === 1) {
return 1
} //设定终止条件
return n * factor(n-1)
} //自己调自己
factor(5)
// 求 1+2+...+n 的值
function sum(n){
if(n === 1) {
return 1
} //设定终止条件
return n + sum(n-1)
} //自己调自己
sum(10)

code:以下代码输出什么

1
2
3
4
5
6
7
8
9
10
11
12
function getInfo(name, age, sex){
console.log('name:',name);
console.log('age:', age);
console.log('sex:', sex);
console.log(arguments);
arguments[0] = 'valley';
console.log('name', name);
}
getInfo('百度', 2, '男');
getInfo('小明', 3);
getInfo('男');

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function getInfo(name, age, sex){
console.log('name:',name);
console.log('age:', age);
console.log('sex:', sex);
console.log(arguments);
arguments[0] = 'valley';
console.log('name', name);
}
getInfo('百度', 2, '男');
// name:百度
// age: 2
// sex: 男
// ['百度', 2, '男']
// name valley
getInfo('小明', 3);
// name: '小明'
// age: 3
// sex: undefined
// ['小明', 3]
// name valley
getInfo('男');
// name: '小明'
// age: undefined
// sex: undefined
// ['男']
// name valley

code:写一个函数,返回参数的平方和

1
2
3
4
5
6
7
8
function sumOfSquares(){
}
var result1 = sumOfSquares(2,3,4);
var result2 = sumOfSquares(1,3);
console.log(result1); //29
console.log(result2); //10

explain

1
2
3
4
5
6
7
8
9
10
11
12
function sumOfSquares(){
var result = 0;
for(var i=0;i<arguments.length;i++){
result += arguments[i] * arguments[i];
}
return result;
}
var result1 = sumOfSquares(2,3,4);
var result2 = sumOfSquares(1,3);
console.log(result1); //29
console.log(result2); //10

code:如下代码的输出?为什么

1
2
3
console.log(a);
var a = 1;
console.log(b);

explain

1
2
3
4
5
6
7
8
9
10
11
console.log(a);
var a = 1;
console.log(b);
// undefined
// Uncaught ReferenceError: b is not defined
代码等同于
var a;
console.log(a); // undefined,因为 a 声明了但没有赋值
a=1;
console.log(b); // 报错,因为 b 没有声明

code:如下代码的输出?为什么

1
2
3
4
5
6
7
8
sayName('world');
sayAge(10);
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sayName('world');
sayAge(10);
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
// hello world
// 报错,ncaught TypeError: sayAge is not a function
//等同于
function sayName(name){
console.log('hello ', name);
}
var sayAge;
sayName('world'); // hello world
sayAge(10); //报错,因为sayAge() 函数没有声明
sayAge = function(age){
console.log(age);
}
// 因为函数的 function 声明会前置,而 var 构造函数表达式,不会前置

code:如下代码输出什么? 写出作用域链查找过程伪代码

1
2
3
4
5
6
7
8
9
var x = 10
bar()
function foo() {
console.log(x)
}
function bar(){
var x = 30
foo()
}

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var x = 10
bar()
function foo() {
console.log(x)
}
function bar(){
var x = 30
foo()
}
// 输出 10
//等同于
var x;
x= 10;
function foo(){
console.log(x);//x是全局变量的x,所以是10
}
function bar(){
var x = 30;
foo(); //只是执行函数foo,foo的所在的作用域是声明函数所在的作用域,而不是调用所在的作用域,故是全局作用域
}
bar();//输出 10
//作用域链查找的伪代码过程如下
//进入全局执行上下文
globalContext = {
AO: {
x: 10,
foo: function,
bar: function,
};
Scope: null;
foo.[Scope] = globalContext.AO;
bar.[Scope] = globalContext.AO;
执行 bar();
}
//进入bar()的执行上下文 从globalContext。AO进入
barContext = {
AO: {
x: 30,
};
Scope: globalContext.AO;
执行 foo(); // 在这个作用域内找不到,就从Scope中去找
}
//进入 foo()的执行上下文 从globalContext.AO进入
fooContext = {
AO: {};
Scope: globalContext.AO;
执行 console.log(x) // 输出10,因为 foo()的作用域是globalContext.AO
}

code:如下代码输出什么? 写出作用域链查找过程伪代码

1
2
3
4
5
6
7
8
9
var x = 10;
bar()
function bar(){
var x = 30;
function foo(){
console.log(x)
}
foo();
}

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
var x = 10;
bar()
function bar(){
var x = 30;
function foo(){
console.log(x)
}
foo();
}
// 输出30
//等同于
function bar(){
function foo(){
console.log(x);
}
var x ; //x是undefined
x = 30; //x变为30
foo();
}
var x;
x = 10;
bar();//输出x为30
//作用域链查找的伪代码过程如下
//进入全局执行上下文
globalContext = {
AO: {
x: 10,
bar: function,
};
Scope: null;
bar.[Scope] = globalContext.AO;
执行 bar();
}
//进入 bar()的执行上下文 从 globalContext.AO 进入
barContext = {
AO: {
x: 30,
foo: function,
};
Scope: globalContext.AO;
foo.[Scope] = barContext.AO;
执行 foo();
}
//进入 foo()的执行上下文 从 barContext.AO 进入
fooContext = {
AO: {};
Scope: barContext.AO;
执行 console.log(x) // 输出 30,因为在 AO 中找不到,就从 Scope 中找
}

code:以下代码输出什么? 写出作用域链的查找过程伪代码

1
2
3
4
5
6
7
8
var x = 10;
bar();
function bar(){
var x = 30;
(function (){
console.log(x);
})();
}

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var x = 10;
bar();
function bar(){
var x = 30;
(function (){
console.log(x);
})();
}
// 输出30
等同于
function bar(){
var x ;
x = 30;
(function (){
console.log(x);
})();
}
var x;
x = 10;
bar();//输出30
//作用域链查找伪代码如下
//进入全局的执行上下文:
globalContext = {
AO: {
X: 10,
bar: function,
};
Scope: null;
bar.[Scope] = globalContext.AO;
执行 bar();
}
//进入 bar() 的执行上下文
barContext = {
AO: {
x: 30,
function: function,
};
Scope: globalContext.AO;
function.[Scope] = barContext.AO;
执行 function ()
}
//进入 function() 的执行上下文
functionContext = {
AO: {};
Scope: barContext.AO
执行 console.log(x) // 输出30 因为 AO 中没有,就从 Scope 中去找
}

code:以下代码输出什么? 写出作用域链查找过程伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var a = 1;
function fn(){
console.log(a);
var a = 5;
console.log(a);
a++;
var a;
fn3();
fn2();
console.log(a);
function fn2(){
console.log(a);
a = 20;
}
}
function fn3(){
console.log(a);
a = 200;
}
fn();
console.log(a);

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
var a = 1;
function fn(){
console.log(a);
var a = 5;
console.log(a);
a++;
var a;
fn3();
fn2();
console.log(a);
function fn2(){
console.log(a);
a = 20;
}
}
function fn3(){
console.log(a);
a = 200;
}
fn();
console.log(a);
// 最终输出为 undefined 5 1 6 20 200
//等同于
function fn(){
function fn2(){
console.log(a);
a = 20; //没有加var,改变fn函数所在的作用域变量a的值为20
}
var a;
console.log(a);//输出undefined
a = 5;
console.log(a);//输出5
a++;
fn3();//输出1
fn2();//输出6
console.log(a);//输出20
}
function fn3(){
console.log(a);
a = 200; //没有加var,改变fn3函数所在的作用域变量a的值为200
}
var a ;
a = 1;
fn();
console.log(a);//输出200
//作用域链查找伪代码如下
//进入全局执行上下文
globalContext = {
AO: {
a: 1,
fn: function,
fn3: function,
};
Scope: null
fn.[Scope] = globalContext.AO;
fn3.[Scope] = globalContext.AO;
执行 fn();
}
//进入 fn() 的执行上下文
fnContext = {
var a;
function fn2(){}
fn2.[Scope] = fnContext
console.log(a) // undefined [1] var 声明前置未赋值
a = 5
console.log(a) // 5 [2] a 已经赋值 5
a++
执行 fn3(){
console.log(a) // 1 [3] fn3 的作用域为 globalContext
a = 200 //改变了 globalContext 中的 a
}
执行 fn2(){
console.log(a) // 6 [4] fn2 的作用域为 fnContext
a = 20 // 改变了 fnContext 中的 a 为 20
}
console.log(a) // 20 [5] a 已经赋值 20
}
console.log(a) // 200 [6] 它的作用域为globalContext
// 最终输出为 undefined 5 1 6 20 200

JavaScript引用类型、对象拷贝

引用类型和基本类型

引用类型

  • 保存在堆内存中的对象,变量中保存的实际上只是一个指针,这个指针执行内存中的另一个位置,由该位置保存对象
  • 对象、数组、函数、正则都是引用类型

基本类型

  • 保存在栈内存中的简单数据段
  • 数值、布尔值、null和undefined都是基本类型

code:如下代码输出什么?

1
2
3
4
5
var obj1 = {a:1, b:2};
var obj2 = {a:1, b:2};
console.log(obj1 == obj2);
console.log(obj1 = obj2);
console.log(obj1 == obj2);

explain

1
2
3
4
5
6
7
8
9
10
11
var obj1 = {a:1, b:2};
var obj2 = {a:1, b:2};
console.log(obj1 == obj2);
// false
// 对象比较的是两个地址,两个对象的地址不同
console.log(obj1 = obj2);
// Oject{a:1 , b:2}
// obj2把地址赋给obj1
console.log(obj1 == obj2);
// true
// 因为对象比较两个地址,前面已经把obj2的地址赋给了obj1,所以地址相同

code:如下代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = 1;
var a = 1;
var b = 2;
var c = {name : 'xiaoming', age : 2 }
var d = [a, b, c];
var aa = a;
var bb = b;
var cc = c;
var dd = d;
a = 11;
b = 22;
c.name = 'hello';
d[2]['age'] = 3;
console.log(aa);
console.log(bb);
console.log(cc);
console.log(dd);

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var a = 1;
var b = 2;
var c = {name : 'xiaoming', age : 2 }
var d = [a, b, c];
var aa = a;
var bb = b;
var cc = c;
var dd = d;
a = 11;
b = 22;
c.name = 'hello';
d[2]['age'] = 3;
console.log(aa);
// 1
// 基本类型传完后互相独立
console.log(bb);
// 2
// 基本类型传完后互相独立
console.log(cc);
// Object{name : "hello", age : 2}
// 引用类型传完后互相关联,指向同一个对象
console.log(dd);
// [1,2,{name: 'hello', age: 3}]
// 引用类型传完后互相关联,指向同一个对象

code:如下代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = 1;
var c = { name: 'xiaoming', age: 2 }
function f1(n){
++n;
}
function f2(obj){
++obj.age;
}
f1(a);
f2(c);
f1(c.age);
console.log(a);
console.log(c);

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = 1;
var c = { name: 'xiaoming', age: 2 }
function f1(n){
++n;
}
function f2(obj){
++obj.age;
}
f1(a);
f2(c);
f1(c.age);
console.log(a);
// 1
// 函数的形参和实参是两个变量,如果保存的是基本类型,形参和实参互相独立
console.log(c);
// Ojbect{name:'xiaoming',age:3}
// 函数的形参和实参是两个变量,如果保存的是引用类型,形参和实参相互关联

code:过滤如下数组,只保留正数,直接在原数组上操作

1
2
3
4
5
6
var arr = [3,1,0,-1,-3,2,-5];
function filter(arr){
}
filter(arr);
console.log(arr); //[3,1,2]

explain

1
2
3
4
5
6
7
8
9
10
11
12
var arr = [3,1,0,-1,-3,2,-5];
function filter(arr){
for(var i = 0; i< arr.length ;){
if(arr[i] <= 0){
arr.splice(i,1)
}else{
i++;
}
}
}
filter(arr);
console.log(arr); //[3,1,2]

code:过滤如下数组,只保留正数,原数组不变,生成新数组

1
2
3
4
5
6
7
var arr = [3,1,0,-1,-3,2,-5]
function filter(arr){
}
var arr2 = filter(arr)
console.log(arr2) // [3,1,2]
console.log(arr) // [3,1,0,-1,-2,2,-5]

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var arr = [3,1,0,-1,-3,2,-5]
function filter(arr){
var j=0;
var arr2 = [];
for(var i=0; i<arr.length; i++){
if(arr[i] > 0){
arr2[j] = arr[i];
j++;
}
}
return arr2;
}
var arr2 = filter(arr);
console.log(arr2); // [3,1,2]
console.log(arr); // [3,1,0,-1,-2,2,-5]

对象拷贝

code:对象深拷贝

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 函数一: 元素是对象则递归成基本类型再拷贝,层层剥皮(递归方法一)
function deepCopy(oldObj) {
var newObj = {};
for(var key in oldObj) {
if(!oldObj.hasOwnProperty(key)){continue;} // 元素不独立就跳出遍历循环
if(typeof oldObj[key] === 'object') {
newObj[key] = deepCopy(oldObj[key]); // 元素是对象,则递归深入拷贝
}else{
newObj[key] = oldObj[key];
}
}
return newObj;
}
// 函数二: 元素是对象则递归成基本类型再拷贝,层层剥皮(递归方法二)
function copy(oldObj) {
var newObj = {};
for(var key in oldObj){
if(!oldObj.hasOwnProperty(key)){continue;} // 元素不独立就跳出遍历循环
if( typeof oldObj[key] === 'number' ||
typeof oldObj[key] === 'string' ||
typeof oldObj[key] === 'boolean' ||
typeof oldObj[key] === 'undefined' ||
oldObj[key] === null ){
newObj[key] = oldObj[key];
}else{
newObj[key] = copy(oldObj[key]); // 元素不是基本类型,则递归深入拷贝
}
}
return newObj;
}
// 函数三: 把 JSON 对象转化成字符串拷贝后,再转化成 JSON 对象(JSON对象转化法)
function jsonCopy(oldObj) {
var newObj = {};
newObj = JSON.parse(JSON.stringify(oldObj));
return newObj;
}

JavaScript字符串、JSON

字符串

code:使用数组拼接出如下字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var prod = {
name : '女装',
style : ['短款','冬季','春装']
};
function getTplStr(data){
//todo...
}
var result = getTplStr(prod); //result为下面的字符串
/*
<dl class = "product">
<dt>女装</dt>
<dd>短款</dd>
<dd>冬季</dd>
<dd>春装</dd>
</dl>
*/

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var prod = {
name : '女装',
style : ['短款','冬季','春装']
};
function getTplStr(data){
var str = '';
str += '<dl class="product">\n';
str += '\t<dt>' + data.name + '</dt>\n';
for(var key in data.style){
str += '\t<dd>'+ data.style[key] + '</dd>\n';
}
str += '</dl>';
return str;
}
var result = getTplStr(prod); //result为下面的字符串
/*
<dl class = "product">
<dt>女装</dt>
<dd>短款</dd>
<dd>冬季</dd>
<dd>春装</dd>
</dl>
*/

code:写出两种以上声明多行字符串的方法

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 反斜杠换行法
var str1 = 'html\
css\
js\
ajax\
';
//斜杠后面不能有空格
console.log(str1);
//字符串拼接法
var str2 = '女装'
+ '短款'
+ '冬季'
+ '春装';
console.log(str2);
// 函数的注释法
function fn(){/*
aaaa
bbbb
cccc
dddd
*/}
var str = fn.toString().split('\n').slice(1,length-1).join('');
/*toString() 把函数 fn 转化成字符串
split('\n') 把字符串以换行符‘\n’为切割点切割成数组
slice(1,-1) 把数组掐头去尾截取出来
join('') 把数组以空字符串''为连接点连接成字符串
*/
console.log(str)
// aaaabbbbccccdddd

code:补全如下代码,让输出结果为字符串:hello\\小明

1
2
var str = //补全代码
console.log(str);

explain

1
2
var str = 'hello\\\\小明'
console.log(str);

code:以下代码输出什么?

explain

1
2
var str = 'baidu\nxiaoming';
console.log(str.length); // 14,\n算一个字符串

code:写一个函数,判断一个字符串是回文字符串

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//用数组的 reverse() 倒叙法
var str = 'abcdedcba'
function isReverse(newStr){
return newStr === newStr.split('').reverse().join('')
}
isReverse(str) // true
/*split('') 把字符串每个字切割成数组元素
reverse() 把数组倒叙排列
join('')把数组连接成字符串
*/
// 倒叙遍历数组的方法
var str = 'abcdedcba'
function isReverse(newStr){
var arr1 = [];
var arr2 = newStr.split('');
var i = 0;
for(var n = 0; n < arr2.length; n++){
arr1[i] = arr2[arr2.length-1-n];
i++;
}
return arr1.join('') === arr2.join(''); // arr1 和 arr2是数组,要连成字符串比较。
}
isReverse(str); // true

code:写一个函数,统计字符串里出现频率最多的字符

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var str = 'wsdfvsagvfdbafdgasdfasdfdsf';
function getCount(str){
var obj = {};
for(var i = 0; i < str.length; i++){
var index = str[i];
if(obj[index]){
obj[index]++; // 如果此字符串出现过,次数加1
}else{
obj[index] = 1; //如果没出现过,次数赋值为1
}
} //把字符串统计成对象
var maxNumber = 0,
maxString = "";
for(var key in obj){
if(obj[key] > maxNumber){
maxNumber = obj[key];
maxString = key;
}
} // 遍历对象寻找最大的键值和键名
return '字符' + maxString + '出现频率最多' + maxNumber + '次';
}
var a = getCount(str);
console.log(a); // "字符d出现频率最多6次"

code:写一个camelize函数,把my-short-string形式的字符串转化成myShortString形式的字符串

explain

1
2
3
4
5
6
7
8
9
10
function camelize(str){
var newArr = str.split('-');
var newStr = '';
for(var i = 0; i < newArr.length;i++){
newStr += newArr[i][0].toUpperCase() + newArr[i].slice(1);
}
return newStr.charAt(0).toLowerCase() + newStr.slice(1);
}
camelize('background-color'); // "backgroundColor"
camelize('list-style-image'); // "listStyleImage"

code:写一个ucFirst函数,返回第一个字母为大写的字符串

explain

1
2
3
4
5
6
function ucFirst(str){
var newStr = str.replace(str[0],str[0].toUpperCase());
return newStr;
}
ucFirst('xiaoming'); //Xiaoming

code:写一个函数truncate(str,maxlength),如果str的长度大于maxlength,会把str截断到maxlength,并加上…

explain

1
2
3
4
5
6
7
8
9
10
11
12
function truncate(str, maxlength){
if(str.length > maxlength){
return str.slice(0,maxlength) + "...";
}else{
return str;
}
}
truncate("hello, my name is xiaoming,", 10);
// "hello, my ..."
truncate("hello world", 20);
// "hello world"

JSON

JSON的简介

  • JSON 是文本格式,能用于在不同编程语言中交换结构化数据
  • 大部分编程语言中存储文本数据的数据类型,在这些编程语言中你可以把 JSON (文本)存储在字符串内
  • JSON抄袭js的语法,但是它有严格的语法规范

JSON语法规则

  • 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象
  • 简单类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity, -Infinity和undefined)
  • 字符串必须使用双引号表示,不能使用单引号
  • 对象的键名必须放在双引号里面
  • 数组或对象最后一个成员的后面,不能加逗号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 以下是合格的JSON值
["one", "two", "three"]
{ "one": 1, "two": 2, "three": 3 }
{"names": ["张三", "李四"] }
[ { "name": "张三"}, {"name": "李四"} ]
// 以下是不合格的JSON值
{ name: "张三", 'age': 32 } // 属性名必须使用双引号
[32, 64, 128, 0xFFF] // 不能使用十六进制值
{ "name": "张三", "age": undefined } // 不能使用undefined
{ "name": "张三",
"birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
"getName": function() {
return this.name;
}
} // 不能使用函数和日期对象

JSON对象

  • ES5新增了JSON对象,用来处理JSON格式数据。它有两个方法:JSON.stringify()和JSON.parse()

JSON.stringify()

  • JSON.stringify方法用于将一个值转为字符串。该字符串符合 JSON 格式,并且可以被JSON.parse方法还原
1
2
3
4
5
6
7
8
9
10
11
JSON.stringify('abc') // ""abc""
JSON.stringify(1) // "1"
JSON.stringify(false) // "false"
JSON.stringify([]) // "[]"
JSON.stringify({}) // "{}"
JSON.stringify([1, "false", false])
// '[1,"false",false]'
JSON.stringify({ name: "张三" })
// '{"name":"张三"}'

JSON.parse()

  • JSON.parse方法用于将JSON字符串转化成对象
1
2
3
4
5
6
7
8
JSON.parse('{}') // {}
JSON.parse('true') // true
JSON.parse('"foo"') // "foo"
JSON.parse('[1, 5, "false"]') // [1, 5, "false"]
JSON.parse('null') // null
var o = JSON.parse('{"name": "张三"}');
o.name // 张三

code:JSON和字符串互相转化

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
name: "cg",
age: 25,
address: {
country: "China",
city: "Beijing",
university: "CUP"
}
}
var str = JSON.stringify(obj); // 把对象转化成字符串
var obj2 = JSON.parse(str); // 把字符串转化成对象

JavaScript的Math对象、数组操作、Date对象

Math对象

  • Math是JavaScript的内置对象,提供一系列数学常数和数学方法
  • 该对象不是构造函数,不能生成实例,所有的属性和方法都必须在Math对象上调用

code:写一个函数返回从min到max之间的随机整数,包括min不包括max

explain

1
2
3
4
function randomNumber(min,max){
return Math.floor(Math.random()*(max-min)+min);
}
randomNumber(10,15); // 12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 运行1000次检验函数符不符合要求
function randomNumber(min, max){
var count = 1000;
var obj = {};
for(var i = 0; i<count ; i++){
var randInt = Math.floor(Math.random()*(max - min))+min;
var key = randInt;
if(obj[key]){
obj[key]++;
}else{
obj[key] = 1;
}
}
for(key in obj){
obj[key] = obj[key] / count;
}
console.log(obj);
}
randomNumber(10, 15);
/*运行1000次,随机整数的概率
10: 0.193,
11: 0.207,
12: 0.19,
13: 0.211,
14: 0.199
包括min不包括max ,符合要求
*/

code:写一个函数返回从min到max之间的随机整数,包括min包括max

explain

1
2
3
4
function randomNumber(min,max){
return Math.floor(Math.random()*(max + 1 - min)+min);
}
randomNumber(10,15); // 12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 运行1000次检验函数符不符合要求
function randomNumber(min, max){
var count = 1000;
var obj = {};
for(var i =0; i<count; i++){
var randInt = Math.floor(Math.random()*(max + 1 - min))+min;
var key = randInt;
if(obj[key]){
obj[key]++;
}else{
obj[key] = 1;
}
}
for(key in obj){
obj[key] = obj[key] / count;
}
console.log(obj);
}
randomNumber(10, 15);
/*运行1000次,随机整数的概率
10: 0.179,
11: 0.18,
12: 0.161,
13: 0.168,
14: 0.152,
15: 0.16
包括min包括max ,符合要求
*/

code:写一个函数,生成一个长度为n的随机字符串,字符串字符的取值范围包括0到9,a到z,A到Z

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function randomStr(n){
var dict = "0123456789" +
"abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var str = "";
for(var i=0; i<n; i++){
str += dict[Math.floor(Math.random()*dict.length)];
}
return str;
}
randomStr(10)
// "E1ein7HnG5" 随机生成一个长度为 10 的字符串
// 运行5次,检验函数符不符合要求
var count = 5;
for(var j=0; j<count; j++){
console.log(randomStr(10));
}
/*运行5次,生成5个这样的字符串
"E1ein7HnG5"
"vvh9pCIbv6"
"xKfiNWZKKC"
"d9dDFTzsOt"
"yt5ZHJNppy"
*/

code:写一个函数,生成一个随机IP地址,一个合法的IP地址为0.0.0.0~255.255.255.255

explain

1
2
3
4
5
6
7
8
9
10
function getRandIp(){
var newIp = "";
for(var i = 0; i < 4; i++){
newIp += Math.floor(Math.random()*256)+'.';
}
return newIp.slice(0,newIp.length-1);
}
var ip = getRandIp();
console.log(ip); // "63.103.212.64"

code:写一个函数,生成一个随机颜色字符串,合法的颜色为#000000~#ffffff

explain

1
2
3
4
5
6
7
8
9
10
function getRandColor(){
var dict = "0123456789abcdef";
var colorStr = "#";
for(var i=0; i<6; i++){
colorStr += dict[Math.floor(Math.random()*dict.length)];
}
return colorStr;
}
var color = getRandColor()
console.log(color) // "#8b0d87"

数组操作

数组方法

push()

  • push方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组
1
2
3
4
5
6
var a = [];
a.push(1) // 1
a.push('a') // 2
a.push(true, {}) // 4
a // [1, 'a', true, {}]

pop()

  • pop方法用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组
1
2
3
4
var a = ['a', 'b', 'c'];
a.pop() // 'c'
a // ['a', 'b']
  • push和pop结合使用,就构成了“后进先出”的栈结构(stack)

shift()

  • shift方法用于删除数组的第一个元素,并返回该元素。注意,该方法会改变原数组
1
2
3
4
var a = ['a', 'b', 'c'];
a.shift() // 'a'
a // ['b', 'c']
  • push和shift结合使用,就构成了“先进先出”的队列结构(queue)

unshift()

  • unshift方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组
1
2
3
4
5
6
7
8
9
var a = ['a', 'b', 'c'];
a.unshift('x'); // 4
a // ['x', 'a', 'b', 'c']
// unshift方法可以在数组头部添加多个元素
var arr = [ 'c', 'd' ];
arr.unshift('a', 'b') // 4
arr // [ 'a', 'b', 'c', 'd' ]

join()

  • join方法以参数作为分隔符,将所有数组成员组成一个字符串返回。如果不提供参数,默认用逗号分隔
1
2
3
4
5
var a = [1, 2, 3, 4];
a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"

splice()

  • splice方法用于删除原数组的一部分成员,并可以在被删除的位置添加入新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组
  • splice的第一个参数是删除的起始位置,第二个参数是被删除的元素个数。如果后面还有更多的参数,则表示这些就是要被插入数组的新元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 从原数组4号位置,删除了两个数组成员
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2) // ["e", "f"]
a // ["a", "b", "c", "d"]
// 除了删除成员,还插入了两个新成员
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2, 1, 2) // ["e", "f"]
a // ["a", "b", "c", "d", 1, 2]
// 起始位置如果是负数,就表示从倒数位置开始删除
// 从倒数第四个位置c开始删除两个成员
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(-4, 2) // ["c", "d"]
//如果只是单纯地插入元素,splice方法的第二个参数可以设为0
var a = [1, 1, 1];
a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]
//如果只提供第一个参数,等同于将原数组在指定位置拆分成两个数组
var a = [1, 2, 3, 4];
a.splice(2) // [3, 4]
a // [1, 2]

concat()

  • concat方法用于多个数组的合并。它将新数组的成员,添加到原数组的尾部,然后返回一个新数组,原数组不变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
['hello'].concat(['world'])
// ["hello", "world"]
['hello'].concat(['world'], ['!'])
// ["hello", "world", "!"]
// concat方法也可以用于将对象合并为数组,但是必须借助call方法
[].concat.call({a: 1}, {b: 2})
// [{ a: 1 }, { b: 2 }]
[].concat.call({a: 1}, [2])
// [{a: 1}, 2]
[2].concat({a: 1})
// [2, {a: 1}]

reverse()

  • reverse方法用于颠倒数组中元素的顺序,返回改变后的数组。注意,该方法将改变原数组
1
2
3
4
var a = ['a', 'b', 'c'];
a.reverse() // ["c", "b", "a"]
a // ["c", "b", "a"]

sort()

  • sort方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd']
[4, 3, 2, 1].sort()
// [1, 2, 3, 4]
[11, 101].sort()
// [101, 11]
[10111, 1101, 111].sort()
// [10111, 1101, 111]
// 如果想让sort方法按照自定义方式排序,可以传入一个函数作为参数,表示按照自定义方法进行排序
[10111, 1101, 111].sort(function (a, b) {
return a - b;
})
// [111, 1101, 10111]

map()

  • map方法对数组的所有成员依次调用一个函数,根据函数结果返回一个新数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var numbers = [1, 2, 3];
numbers.map(function (n) {
return n + 1;
});
// [2, 3, 4]
numbers
// [1, 2, 3]
[1, 2, 3].map(function(elem, index, arr) {
return elem * index;
});
// [0, 2, 6]
var upper = function (x) {
return x.toUpperCase();
};
[].map.call('abc', upper) // 原本this是指向[],变成指向'abc'
// [ 'A', 'B', 'C' ]

forEach()

  • forEach方法与map方法很相似,也是遍历数组的所有成员,执行某种操作,但是forEach方法一般不返回值,只用来操作数据
1
2
3
4
5
6
7
8
function log(element, index, array) {
console.log('[' + index + '] = ' + element);
}
[2, 5, 9].forEach(log);
// [0] = 2
// [1] = 5
// [2] = 9

filter()

  • filter方法的参数是一个函数,所有数组成员依次执行该函数,返回结果为true的成员组成一个新数组返回。该方法不会改变原数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[1, 2, 3, 4, 5].filter(function (elem) {
return (elem > 3);
})
// [4, 5]
var arr = [0, 1, 'a', false];
arr.filter(Boolean)
// [1, "a"]
[1, 2, 3, 4, 5].filter(function (elem, index, arr) {
return index % 2 === 0;
});
// [1, 3, 5]

some(),every()

  • 这两个方法类似“断言”(assert),用来判断数组成员是否符合某种条件
  • some方法是只要有一个数组成员的返回值是true,则整个some方法的返回值就是true,否则false
  • every方法则是所有数组成员的返回值都是true,才返回true,否则false
1
2
3
4
5
6
7
8
9
10
11
12
13
// 如果存在大于等于3的数组成员,就返回true
var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
return elem >= 3;
});
// true
// 只有所有数组成员大于等于3,才返回true
var arr = [1, 2, 3, 4, 5];
arr.every(function (elem, index, arr) {
return elem >= 3;
});
// false

reduce(),reduceRight()

  • reduce方法和reduceRight方法依次处理数组的每个成员,最终累计为一个值
  • reduce是从左到右处理(从第一个成员到最后一个成员),reduceRight则是从右到左(从最后一个成员到第一个成员),其他完全一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 下面的例子求数组成员之和
[1, 2, 3, 4, 5].reduce(function(x, y){
console.log(x, y)
return x + y;
});
// 1 2
// 3 3
// 6 4
// 10 5
//最后结果:15
// 下面是一个reduceRight方法的例子
function substract(prev, cur) {
return prev - cur;
}
[3, 2, 1].reduce(substract) // 0
[3, 2, 1].reduceRight(substract) // -4

indexOf(),lastIndexOf()

  • indexOf方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1
1
2
3
4
5
6
7
8
9
10
11
12
// indexOf方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1
var a = ['a', 'b', 'c'];
a.indexOf('b') // 1
a.indexOf('y') // -1
// indexOf方法还可以接受第二个参数,表示搜索的开始位置
['a', 'b', 'c'].indexOf('a', 1) // -1
// lastIndexOf方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1
var a = [2, 5, 9, 2];
a.lastIndexOf(2) // 3
a.lastIndexOf(7) // -1

code:用splice函数分别实现push、pop、shift、unshift方法

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 实现push
var a = [1,2,3,4,5];
a.splice(a.length,0,6) //返回[];原数组变成[1,2,3,4,5,6]
// 实现pop
var a = [1,2,3,4,5];
a.splice(a.length-1,1) //返回[5];原数组变成[1,2,3,4]
// 实现shift
var a = [1,2,3,4,5];
a.splice(0,1); //返回[1],原数组变成[2,3,4,5]
// 实现unshift
var a = [1,2,3,4,5];
a.splice(0,0,0); //返回[];原数组变成[0,1,2,3,4,5]

code:写一个函数,操作数组,数组中的每一项变为原来的平方,在原数组上操作

explain

1
2
3
4
5
6
7
8
9
function squareArr(arr){
for(var i = 0; i < arr.length; i++){
arr[i] = arr[i]*arr[i];
}
return arr;
}
var arr = [2, 4, 6]
squareArr(arr)
console.log(arr) // [4, 16, 36]

code:写一个函数,操作数组,返回一个新数组,新数组中只包含正数,原数组不变

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function filterPositive(arr){
var newArr = [];
var j = 0;
for(var i = 0; i < arr.length; i++){
if( typeof arr[i] ==="number" && arr[i] > 0){
newArr[j] = arr[i];
j++;
}
}
return newArr;
}
var arr = [3, -1, 2, '小明', true]
var newArr = filterPositive(arr)
console.log(newArr) // [3, 2]
console.log(arr) // [3, -1, 2, '小明', true]

Date对象

  • Date对象是JavaScript提供的日期和时间的操作接口。它可以表示的时间范围是,UTC时间的1970年1月1日00:00:00前后的各1亿天(单位为毫秒)
  • UTC 世界标准时间
  • CST 北京时间(东八区,比世界标准时间快(早)8小时,即如果北京时间是08:00:00,UTC时间是00:00:00)

new Date()

  • Date还可以当作构造函数使用。对它使用new命令,会返回一个Date对象的实例
  • 如果不加参数,生成的就是代表当前时间的对象
  • 作为构造函数时,Date对象可以接受多种格式的参数
1
2
3
4
var today = new Date();
today
// "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"

new Date(milliseconds)

  • Date对象接受从1970年1月1日00:00:00 UTC开始计算的毫秒数作为参数
  • Unix时间戳(单位为秒)作为参数,必须将Unix时间戳乘以1000
1
2
3
4
5
6
new Date(1378218728000)
// Tue Sep 03 2013 22:32:08 GMT+0800 (CST)
// 1970年1月2日的零时
var Jan02_1970 = new Date(3600 * 24 * 1000);
// Fri Jan 02 1970 08:00:00 GMT+0800 (CST)

new Date(datestring)

  • Date对象还接受一个日期字符串作为参数,返回所对应的时间,日期字符串的完整格式是“month day, year hours:minutes:seconds”
  • 其他格式的日期字符串,也可以被解析
1
2
3
4
5
6
7
8
9
10
11
12
13
new Date('January 6, 2013');
// Sun Jan 06 2013 00:00:00 GMT+0800 (CST)
new Date('2013-2-15')
new Date('2013/2/15')
new Date('02/15/2013')
new Date('2013-FEB-15')
new Date('FEB, 15, 2013')
new Date('FEB 15, 2013')
new Date('Feberuary, 15, 2013')
new Date('Feberuary 15, 2013')
new Date('15 Feb 2013')
new Date('15, Feberuary, 2013')
  • 注意,在ES5(ES6取消)之中,如果日期采用连词线(-)格式分隔,且具有前导0,JavaScript会认为这是一个ISO格式的日期字符串,导致返回的时间是以UTC时区计算的
1
2
3
4
5
new Date('2014-01-01')
// Wed Jan 01 2014 08:00:00 GMT+0800 (CST)
new Date('2014-1-1')
// Wed Jan 01 2014 00:00:00 GMT+0800 (CST)

new Date(year, month [, day, hours, minutes, seconds, ms])

  • Date对象还可以接受多个整数作为参数,依次表示年、月、日、小时、分钟、秒和毫秒
  • 最少需要提供两个参数(年和月),其他参数都是可选的,默认等于0
  • 如果只使用“年”这一个参数,Date对象会将其解释为毫秒数
1
2
new Date(2013)
// Thu Jan 01 1970 08:00:02 GMT+0800 (CST)
  • year:四位年份,如果写成两位数,则加上1900
  • month:表示月份,0表示一月,11表示12月
  • date:表示日期,1到31
  • hour:表示小时,0到23
  • minute:表示分钟,0到59
  • second:表示秒钟,0到59
  • ms:表示毫秒,0到999
1
2
3
4
5
6
7
8
9
10
11
new Date(2013, 0)
// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
new Date(2013, 0, 1)
// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
new Date(2013, 0, 1, 0)
// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
new Date(2013, 0, 1, 0, 0, 0, 0)
// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)

日期的运算

  • 类型转换时,Date对象的实例如果转为数值,则等于对应的毫秒数
  • 如果转为字符串,则等于对应的日期字符串
  • 两个日期对象进行减法运算,返回的就是它们间隔的毫秒数;进行加法运算,返回的就是连接后的两个字符串
1
2
3
4
5
6
7
8
var d1 = new Date(2000, 2, 1);
var d2 = new Date(2000, 3, 1);
d2 - d1
// 2678400000
d2 + d1
// "Sat Apr 01 2000 00:00:00 GMT+0800 (CST)Wed Mar 01 2000 00:00:00 GMT+0800 (CST)"

Date对象的静态方法

Date.now()

  • Date.now方法返回当前距离1970年1月1日 00:00:00 UTC的毫秒数(Unix时间戳乘以1000)
1
Date.now() // 1498121973604

Date.parse()

  • Date.parse方法用来解析日期字符串,返回距离1970年1月1日 00:00:00的毫秒数
1
2
3
4
5
6
7
8
9
10
Date.parse('Aug 9, 1995')
// 返回807897600000,以下省略返回值
Date.parse('January 26, 2011 13:51:50')
Date.parse('Mon, 25 Dec 1995 13:30:00 GMT')
Date.parse('Mon, 25 Dec 1995 13:30:00 +0430')
Date.parse('2011-10-10')
Date.parse('2011-10-10T14:48:00')
Date.parse('xxx') // NaN

Date.UTC()

  • Date.UTC方法可以返回UTC时间(世界标准时间)
  • 该方法接受年、月、日等变量作为参数,返回当前距离1970年1月1日 00:00:00 UTC的毫秒数
1
2
Date.UTC(2011, 0, 1, 2, 3, 4, 567)
// 1293847384567

Date实例对象的方法

  • get类:获取Date对象的日期和时间
  • set类:设置Date对象的日期和时间

get类方法

  • getTime():返回距离1970年1月1日00:00:00的毫秒数,等同于valueOf方法
  • getDate():返回实例对象对应每个月的几号(从1开始)
  • getDay():返回星期几,星期日为0,星期一为1,以此类推
  • getYear():返回距离1900的年数
  • getFullYear():返回四位的年份
  • getMonth():返回月份(0表示1月,11表示12月)
  • getHours():返回小时(0-23)
  • getMilliseconds():返回毫秒(0-999)
  • getMinutes():返回分钟(0-59)
  • getSeconds():返回秒(0-59)
  • getTimezoneOffset():返回当前时间与UTC的时区差异,以分钟表示,返回结果考虑到了夏令时因素
  • getUTCDate()
  • getUTCFullYear()
  • getUTCMonth()
  • getUTCDay()
  • getUTCHours()
  • getUTCMinutes()
  • getUTCSeconds()
  • getUTCMilliseconds()
1
2
3
4
5
6
7
8
9
10
11
12
var d = new Date('January 6, 2013');
d.getDate() // 6
d.getMonth() // 0
d.getYear() // 113
d.getFullYear() // 2013
d.getTimezoneOffset() // -480
var d = new Date('January 6, 2013');
d.getDate() // 6
d.getUTCDate() // 5

set类方法

  • setDate(date):设置实例对象对应的每个月的几号(1-31),返回改变后毫秒时间戳
  • setYear(year): 设置距离1900年的年数
  • setFullYear(year [, month, date]):设置四位年份
  • setHours(hour [, min, sec, ms]):设置小时(0-23)
  • setMilliseconds():设置毫秒(0-999)
  • setMinutes(min [, sec, ms]):设置分钟(0-59)
  • setMonth(month [, date]):设置月份(0-11)
  • setSeconds(sec [, ms]):设置秒(0-59)
  • setTime(milliseconds):设置毫秒时间戳
  • setUTCDate()
  • setUTCFullYear()
  • setUTCHours()
  • setUTCMilliseconds()
  • setUTCMinutes()
  • setUTCMonth()
  • setUTCSeconds()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var d = new Date ('January 6, 2013');
d // Sun Jan 06 2013 00:00:00 GMT+0800 (CST)
d.setDate(9) // 1357660800000
d // Wed Jan 09 2013 00:00:00 GMT+0800 (CST)
var d = new Date();
// 将日期向后推1000天
d.setDate( d.getDate() + 1000 );
// 将时间设为6小时后
d.setHours(d.getHours() + 6);
// 将年份设为去年
d.setFullYear(d.getFullYear() - 1);
// 本地时区(东八时区)的1月6日0点0分,是UTC时区的前一天下午16点
// 设为UTC时区的22点以后,就变为本地时区的上午6点
var d = new Date('January 6, 2013');
d.getUTCHours() // 16
d.setUTCHours(22) // 1357423200000
d // Sun Jan 06 2013 06:00:00 GMT+0800 (CST)

写一个函数getChIntv,获取从当前时间到指定日期的间隔时间

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
function getChIntv(data){
var str = data.split("-").join(","); // 2017,07,01
var date = new Date(str);
var nowDate = new Date();
var diff = date - nowDate; // 总时间差
var getDay = Math.floor( diff / (24 * 60 * 60 * 1000) );
var getHours = Math.floor( diff / (60 * 60 * 1000) ) % 24;
var getMinutes = Math.floor( diff / (60 * 1000) ) % 60;
var getSeconds = Math.floor( diff / 1000) % 60;
return "距离七月一号还有" + getDay + "天" + getHours + "小时" + getMinutes + "分钟" + getSeconds + "秒";
}
var str = getChIntv("2017-07-01");
console.log(str); // "距离七月一号还有8天6小时9分钟17秒"

把hh-mm-dd格式数字日期改成中文日期

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
function getChsDate(data){
var str = data.split("-"); // ["2017", "06", "22"]
var dateArr = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十", "二十一", "二十二", "二十三", "二十四", "二十五", "二十六", "二十七", "二十八", "二十九", "三十", "三十一"];
var year = str[0];
var month = str[1];
var day = str[2];
var getYear = dateArr[ parseInt(year[0]) ] + dateArr[ parseInt(year[1]) ] + dateArr[ parseInt(year[2]) ] + dateArr[ parseInt(year[3]) ];
var getMonth = dateArr[ parseInt(month * 1 ) ];
var getDay = dateArr[ parseInt(day * 1 ) ];
return getYear +'年'+getMonth+'月'+getDay+'日';
}
var str = getChsDate('2017-06-22');
console.log(str); // "二零一七年六月二十二日"

写一个函数,参数为时间对象毫秒数的字符串格式,返回值为字符串。假设参数为时间对象毫秒数t,根据t的时间分别返回如下字符串:

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function friendlyDate(time){
var timeStart = parseInt(time);
var timeEnd = Date.now();
var time = timeEnd - timeStart;
if(time < 1000*60){
return "刚刚";
}else if(time < 1000*60*60){
return "3分钟前";
}else if(time < 1000*60*60*24){
return "8小时前";
}else if(time < 1000*60*60*24*30){
return "3天前";
}else if(time < 1000*60*60*24*30*12){
return "2个月前";
}else{
return "8年前";
}
}
var str = friendlyDate( '1498125388458' )
console.log(str); // "刚刚"
var str2 = friendlyDate('149100529858')
console.log(str2); // "8年前"

正则表达式

>>三十分钟学会正则


JavaScript的DOM操作

  • DOM是JavaScript操作网页的接口,全称为“文档对象模型”(Document Object Model)
  • 它的作用是将网页转为一个JavaScript对象,从而可以用脚本进行各种操作(比如增删内容)
  • DOM的最小组成单位叫做节点(node)。文档的树形结构(DOM树),就是由各种不同类型的节点组成
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My title</title>
</head>
<body>
<a href="#">My link</a>
<h1>My header</h1>
</body>
</html>

images

  • 节点的类型有七种
  • Document:整个文档树的顶层节点
  • DocumentType:doctype标签(比如<!DOCTYPE html>)
  • Element:网页的各种HTML标签(比如、等)
  • Attribute:网页元素的属性(比如class=”right”)
  • Text:标签之间或标签包含的文本
  • Comment:注释
  • DocumentFragment:文档的片段
  • 除了根节点以外,其他节点对于周围的节点都存在三种关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>
<head>
<title>DOM 教程</title>
</head>
<body>
<h1>DOM 第一课</h1>
<p>Hello world!</p>
</body>
</html>
// 空格都是节点,这里忽略
// <html> 节点没有父节点;它是根节点
// <head><body> 的父节点是<html> 节点
// 文本节点 "Hello world!" 的父节点是 <p> 节点
// <html> 节点拥有两个子节点:<head><body>
// <head> 节点拥有一个子节点:<title>
// 节点 <title> 节点也拥有一个子节点:文本节点 "DOM 教程"
// <h1><p> 节点是同胞节点,同时也是<body> 的子节点

images

  • 父节点关系(parentNode):直接的那个上级节点
  • 子节点关系(childNodes):直接的下级节点
  • 同级节点关系(sibling):拥有同一个父节点的节点

element.innerText和element.innerHTML

  • innerText属性返回元素内包含的文本内容,而且会把元素内的标签元素去掉,在多层次的时候会按照元素由浅到深的顺序拼接其内容
  • innerHTML属性返回该元素包含的 HTML 代码,包含了标签元素和文本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<p>
123
<span>456</span>
</p>
</div>
<script>
var oDiv = document.querySelector('div');
console.log(oDiv.innerText); // "123 456"
console.log(oDiv.innerHTML); // "<p>123<span>456</span></p>"
</script>
</body>
</html>

element.children和node.childNodes

  • element.children是返回该元素下的所有子元素对象,不包括文本对象
  • node.childNodes是返回该元素下的所有子对象,包括本文对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="box">
<p>p标签</p>
<span>span标签</span>
<a href="#" class="btn">a链接</a>
</div>
<script>
var box = document.getElementById('box');
console.log(box.children);
// 返回 [p, span, a.btn]
var box = document.getElementById('box');
console.log(box.childNodes);
// 返回 [text, p, text, span, text, a.btn, text]
</script>
</body>
</html>

查询元素常见的方法(查)、ES5的元素选择方法(查)

查询元素常见的方法

  • document.getElementById()
  • document.getElementsByClassName()
  • document.getElementsByTagName()
  • document.getElementsByName()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="box">
<p class="title">p标签</p>
<span class="title">span标签</span>
<a href="#" class="btn">a链接</a>
<a href="#" class="btn">a链接</a>
</div>
<input type="text" name="username">
<input type="password" name="pwd">
<script>
var box = document.getElementById('box'); // 获取id为box的元素
var tit1 = document.getElementsByClassName('title')[0]; // 获取第一个class为title的元素
var tit2 = document.getElementsByClassName('title')[1]; // 获取第二个class为title的元素
var btn = document.getElementsByTagName('a')[0]; // 获取第一个a标签
var user = document.getElementsByName('username')[0]; // 获取第一个name属性为username元素
</script>
</body>
</html>

ES5的元素选择方法

  • document.querySelector()
  • document.querySelectorAll()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="box">
<p class="title">p标签<a href="#" class="btn">a链接</a></p>
<span class="title">span标签</span>
</div>
<input type="text" name="username">
<input type="password" name="pwd">
<script>
var box = document.querySelector('#box'); // 获取id为box的元素
var tit = document.querySelectorAll('.title')[1]; // 获取第二个class为title的元素
var child = document.querySelectorAll('.title a')[0]; // 获取第一个class为title 里面的a元素
var ipt = document.querySelectorAll('[name="username"]')[0]; // 获取第一个name属性为username的表单
</script>
</body>
</html>

创建元素(增)、设置元素属性(改)、删除属性(删)

创建元素

  • 创建元素节点:document.createElement(“div”)
  • 创建文本节点:document.createTextNode(“你好”)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
var newDiv = document.createElement('div'); // 创建元素节点
var newContent = document.createTextNode('你好'); // 创建文本节点
newDiv.appendChild(newContent); // 文本节点放入元素节点
document.body.appendChild(newDiv); // 元素节点放入body
</script>
</body>
</html>

images

设置元素属性

  • 获取元素的属性值:element.getAttribute(“id”)
  • 设置元素的属性值:element.setAttribute(“align”,”center”)
  • 生成一个新的属性对象节点:document.createAttribute(“my_attrib”)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="div1"></div>
<div id="d1"></div>
<script>
var div = document.getElementById('div1');
div.getAttribute('id') // "div1"
var d = document.getElementById('d1');
d.setAttribute('align', 'center');
// HTML代码变为<div id="d1" align="center"></div>
var node = document.getElementById("div1");
var a = document.createAttribute("my_attrib");
a.value = "newVal";
node.setAttributeNode(a);
// HTML代码变为<div id="div1" my_attrib="newVal"></div>
// 等同于
var node = document.getElementById("div1");
node.setAttribute("my_attrib", "newVal");
// HTML代码变为<div id="div1" my_attrib="newVal"></div>
</script>
</body>
</html>

删除元素属性(删)

  • 删除元素属性:element.removeAttribute(“align”)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="div1" align="left" width="200px">
<script>
var oDiv = document.getElementById('div1')
oDiv.removeAttribute('align');
// HTML代码变为<div id="div1" width="200px">
</script>
</body>
</html>

元素插入子元素(改)、删除元素的子元素(删)

元素插入子元素

  • 在元素的末尾插入子元素:node.appendChild(span1)
  • 在某个元素之前插入子元素:node.insertBefore(span2)
  • 替换元素(接受两个参数:要插入的元素和要替换的元素):node.replaceChild(newSpan, divA)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="box1"></div>
<div id="box2"></div>
<script>
var box1 = document.getElementById('box1');
var span1 = document.createElement('span');
span1.innerHTML = '添加一个span1';
box1.appendChild(span1);
// 将span元素插入到box1元素内
var box2 = document.getElementById('box2');
var span2 = document.createElement('span');
span2.innerHTML = '添加一个span2';
box2.appendChild(span2);
document.body.insertBefore(box2,box1);
// 将box2元素插入body元素内的box1元素之前
</script>
</body>
</html>

images

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="A">你好</div>
<script>
var divA = document.getElementById('A');
var newSpan = document.createElement('span');
newSpan.textContent = 'Hello World!';
divA.parentNode.replaceChild(newSpan, divA);
// 将divA元素替换成newSpan元素
</script>
</body>
</html>

images

删除元素的子元素

  • 删除元素的子元素:node.removeChild(box)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="box">123</div>
<script>
var box = document.getElementById('box');
document.body.removeChild(box);
</script>
</body>
</html>

element.classList的方法(HTML5api)

  • 用于在元素中添加,移除及切换CSS类:element.classList
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="box">123</div>
<script>
var element = document.getElementById('box');
element.classList.add('head'); // 添加class属性值为"head"
element.classList.remove('head'); // 删除class属性值为"head"
element.classList.contains('head'); // 包含class属性值为"head"就返回true / 没有则返回flase
element.classList.toggle('head'); // 有则删除class属性值"head"返回false,没有则加上返回true
element.classList.toString(); // 返回全部属性值的字符串
element.classList.length; // 返回全部class属性的长度
</script>
</body>
</html>

code:如何选中如下代码所有的li元素? 如何选中btn元素?

1
2
3
4
5
6
7
8
<div class="mod-tabs">
<ul>
<li>list1<li>
<li>list2<li>
<li>list3<li>
</ul>
<button class="btn">点我</button>
</div>

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="mod-tabs">
<ul>
<li>list1<li>
<li>list2<li>
<li>list3<li>
</ul>
<button class="btn">点我</button>
</div>
// 选中所有的<li>元素
document.getElementsByTagName("li");
document.querySelectorAll("li");
// 选中btn元素
document.getElementsByClassName("btn")[0];
document.getElementsByTagName("button")[0];
document.querySelector(".btn");
document.querySelectorAll(".btn")[0];

JavaScript的事件

DOM0事件和DOM2事件处理方式

DOM0事件处理方式

  • 通过JavaScript制定事件处理程序的传统方式;
  • 把一个方法赋值给一个元素的事件处理程序属性,第四代web浏览器出现,至今所有浏览器都支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.onclick = function () {
console.log(this.id);
};
btnClick.onclick = null; // 删除事件处理程序
</script>
</body>
</html>
  • 一个事件处理程序只能对应一个处理函数,设置第二个事件,因为是赋值,所以第二个事件会覆盖第一个事件

DOM2事件处理方式

  • DOM2级事件定义了两个方法用于处理指定和删除事件处理程序的操作
  • 它们都接受三个参数(事件类型,事件处理方法,布尔参数[true,捕获阶段调用事件处理程序,默认false,事件冒泡阶段处理
    ])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.addEventListener('click', function() {
console.log(this.id);
}, false);
btnClick.removeEventListener('click', function() {
console.log(this.id);
}, false); // 删除事件处理程序
</script>
</body>
</html>

attachEvent和addEventListener

  • 参数个数不相同;attachEvent()接受两个参数,通过attachEvent()添加的事件处理程序会被添加到冒泡阶段;addEventListener()接受三个参数,第三个参数决定添加的处理事件是在捕获阶段还是冒泡阶段处理
  • 第一个参数意义不同;addEventListener第一个参数是事件类型(比如click,load),而attachEvent第一个参数指明的是事件处理函数名称(onclick,onload)
  • 事件处理程序的作用域不相同;addEventListener的作用域是元素本身,this是指的触发元素,而attachEvent事件处理程序会在全局变量内运行,this是window
  • 为一个事件添加多个事件处理程序时,执行顺序不同;addEventListener添加会按照添加顺序执行,而attachEvent添加多个事件处理程序时顺序无规律

IE事件冒泡机制和DOM2事件流

IE的事件冒泡

  • 事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的元素

images

DOM2事件流

  • DOM2事件流包括三个阶段,事件捕获阶段,处于目标阶段,事件冒泡阶段,首先发生的是事件捕获,为截取事件提供机会,然后是实际目标接收事件,最后是冒泡阶段

images

阻止事件冒泡和阻止默认事件

  • 事件发生以后,会生成一个事件对象,作为参数传给监听函数,称为event对象,包含与创建它的特定事件有关的属性和方法

阻止事件冒泡

  • 标准浏览器:event.stopPropagation()
  • IE浏览器:event.cancelBubble = true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="div1">
<input type="button" value="按钮" id="btn1">
</div>
<script type="text/javascript">
// 标准浏览器
document.getElementById('div1').onclick=function(){
console.log('div1 click'); //事件被阻断了,不再响应
}
document.getElementById('btn1').onclick=function(e){
e.stopPropagation();
console.log('btn1 click');
}
// IE浏览器
document.getElementById('div1').onclick=function(){
console.log('div1 click'); //事件被阻断了,不再响应
}
document.getElementById('btn1').onclick=function(e){
e.cancelBubble = true;
console.log('btn1 click');
}
</script>
</body>
</html>

images

阻止默认事件

  • 标准浏览器:event.preventDefault()
  • IE浏览器:e.returnValue = false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<a id="link" href="http://www.baidu.com">百度</a>
<script type="text/javascript">
// 标准浏览器
document.getElementById('link').onclick=function(e){
e.preventDefault();
}
//IE 浏览器
document.getElementById('link').onclick=function(e){
e.returnValue = false;
}
</script>
</body>
</html>

code:如下代码,要求当点击每一个元素li时控制台展示该元素的文本内容。不考虑兼容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<ul class="ct">
<li>这里是</li>
<li>我家</li>
<li>大厅</li>
</ul>
</body>
</html>

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<ul class="ct">
<li>这里是</li>
<li>我家</li>
<li>大厅</li>
</ul>
<script>
// 方法一:直接在给每个li添加事件,注意this不能换成aLi[i];
var aLi = document.getElementsByTagName('li');
for(var i = 0;i<aLi.length;i++){
aLi[i].addEventListener('click',function(){
console.log(this.innerHTML);
})
}
// 事件代理,将事件给ul监听
var oUl = document.getElementsByClassName('ct')[0];
oUl.addEventListener('click',function(e){
console.log(e.target.innerHTML); //target 为发出事件的元素
})
</script>
</body>
</html>

code:按如下要求,补全代码

  • 当点击按钮开头添加时在li这里是/li元素前添加一个新元素,内容为用户输入的非空字符串
  • 当点击结尾添加时在最后一个 li 元素后添加用户输入的非空字符串
  • 当点击每一个元素li时控制台展示该元素的文本内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<ul class="ct">
<li>这里是</li>
<li>我家</li>
<li>大厅</li>
</ul>
<input class="ipt-add-content" placeholder="添加内容">
<button id="btn-add-start">开头添加</button>
<button id="btn-add-end">结尾添加</button>
</body>
</html>

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<ul class="ct">
<li>这里是</li>
<li>我家</li>
<li>大厅</li>
</ul>
<input class="ipt-add-content" placeholder="添加内容">
<button id="btn-add-start">开头添加</button>
<button id="btn-add-end">结尾添加</button>
<script>
function $(str){
return document.querySelector(str);
}
var ul = $(".ct");
var input = $('.ipt-add-content');
var btnStart = $('#btn-add-start');
var btnEnd = $('#btn-add-end');
ul.addEventListener('click',function(e){
console.log(e.target.innerText);
})
btnStart.addEventListener('click',function(){
var newLi = document.createElement('li');
newLi.innerText = input.value;
if(newLi.innerText === ''){
console.log("please input content");
}else{
ul.insertBefore(newLi,ul.firstChild);
}
})
btnEnd.addEventListener('click',function(){
var newLi = document.createElement('li');
newLi.innerText = input.value;
if(newLi.innerText === ''){
console.log("please input content");
}else{
ul.appendChild(newLi,ul);
}
})
</script>
</body>
</html>

code:补全代码,要求:当鼠标放置在li元素上,会在img-preview里展示当前li元素的data-img对应的图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<ul class="ct">
<li data-img="http://ww1.sinaimg.cn/large/007tGA0Agy1fzi0jy43uvj30ez0bxk05">鼠标放置查看图片1</li>
<li data-img="http://ww1.sinaimg.cn/large/007tGA0Agy1fzi0kdr5l0j30di0a2dmu">鼠标放置查看图片2</li>
<li data-img="http://ww1.sinaimg.cn/large/007tGA0Agy1fzi0kj9bx1j30h80dzh01">鼠标放置查看图片3</li>
</ul>
<div class="img-preview"></div>
</body>
</html>

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<ul class="ct">
<li data-img="http://ww1.sinaimg.cn/large/007tGA0Agy1fzi0jy43uvj30ez0bxk05">鼠标放置查看图片1</li>
<li data-img="http://ww1.sinaimg.cn/large/007tGA0Agy1fzi0kdr5l0j30di0a2dmu">鼠标放置查看图片2</li>
<li data-img="http://ww1.sinaimg.cn/large/007tGA0Agy1fzi0kj9bx1j30h80dzh01">鼠标放置查看图片3</li>
</ul>
<div class="img-preview"></div>
<script>
function $(str){
return document.querySelector(str);
}
function $$(str){
return document.querySelectorAll(str);
}
var ct = $('.ct');
var li = $$('.ct > li');
var imgPreview = $('.img-preview');
/* // 方法一:给每个li添加事件
for(var i = 0;i < li.length;i++){
li[i].addEventListener('mouseover',function(){
var img = this.getAttribute('data-img');
imgPreview.innerHTML = '<img src = \"' + img + '\">';
})
}
*/
// 方法二:给ul事件代理
ct.addEventListener('mouseover',function(e){
if(e.target.tagName.toLowerCase() === 'li'){
// var img = document.createElement('img');
// var imgDate = e.target.getAttribute('data-img');
// img.setAttribute('src',imgDate);
// imgPreview.appendChild(img);
var imgDate = e.target.getAttribute('data-img');
imgPreview.innerHTML = "<img src=\'" + imgDate + "\'>";
}
})
ct.addEventListener('mouseout',function(){
imgPreview.innerHTML = "";
})
</script>
</body>
</html>

写一个通用的事件侦听器函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
markyun.Event = {
// 页面加载完成后
readyEvent: function(fn){
if(fn == null){
fn = document
}
var oldonload = window.onload
if(typeof window.onload != 'function'){
window.onload = fn
}else{
window.onload = function(){
oldonload()
fn()
}
}
},
// 兼容使用dom0||dom2||IE方式来绑定事件
// 参数: 操作的元素、事件名称、事件处理程序
addEvent: function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false)
}else if(element.attachEvent){
element.attachEvent('on' + type,function(){
handler.call(element)
})
}else{
element['on' + type] = handler
}
},
// 移除事件
removeEvent: function(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false)
}else if(element.datachEvent){
element.detachEvent('on'+ type,handler)
}else{
element['on' + type] = null
}
},
// 阻止事件
stopPropagation: function(ev){
if(ev.stopPropagation){
ev.stopPropagation()
}else{
ev.cancelBubble = true
}
},
preventDefault: function(event){
if(event.preventDefault){
event.preventDefault()
}else{
event.returnValue = false
}
},
getTarget: function(event){
return event.target || event.srcElement
},
getEvent: function(e){
var ev = e || window.event
if(!ev){
var c = this.getEvent.caller
while(c){
ev = c.arguments[0]
if(ev&&Event==ev.constructor){
break
}
c = c.caller
}
}
return ev
}
}

JavaScript的闭包、定时器、BOM

闭包和立即执行函数(IIFE)

闭包(套路)

  • 函数和函数内部能访问到的变量(也叫环境)的总和,就是一个闭包
  • 创建闭包常见的方式就是在一个函数内部创建另一个函数,并返回当前函数作用域的变量,暴露出变量,让别人可以访问
1
2
3
4
5
6
7
8
9
10
11
function foo(){
var local = 1;
function bar(){
local++;
return local;
}
return bar;
}
var func = foo();
func(); // 2

立即执行函数(IIFE)

  • 声明一个匿名函数,马上调用这个匿名函数
  • 目的:1.是不必为函数命名,避免了污染全局变量;2.是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
1
2
3
4
(function(){
var a = 1;
console.log(a);
}())

code:下面的代码输出多少?修改代码让fnArri输出 i。使用两种以上的方法

1
2
3
4
5
6
7
8
var fnArr = [];
for (var i = 0; i < 10; i++){
fnArr[i] = function(){
return i;
};
}
console.log(fnArr[3]()); // 10
//因为调用函数的时候,全局变量 i 已经变成了10

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//方法1: 闭包加IIFE(立即执行函数)
var fnArr = [];
for (var i = 0; i < 10; i++) {
fnArr[i] = function(i){
return function(){
return i
};
}(i)
}
console.log(fnArr[3]()); // 3
/*在外部嵌套一个立即执行的函数,构造一个闭包,
保存每次循环中的 i 的值。等调用的时候,就读取当时的 i 的值
*/
//方法二:同理方法一
var fnArr = [];
for (var i = 0; i < 10; i++){
!function (i){
fnArr[i] = function(){
return i;
}
}(i);
}
console.log(fnArr[3]()); // 3
// 方法3:用let创建每个作用域的私有变量
var fnArr = [];
for (let i = 0; i < 10; i ++) {
fnArr[i] = function(){
return i;
};
}
console.log(fnArr[3]()); //3

code:封装一个汽车对象,可以通过如下方式获取汽车状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var Car = (function(){
var speed = 0;
function setSpeed(s){
speed = s
}
...
return {
setSpeed: setSpeed,
...
}
})()
Car.setSpeed(30);
Car.getSpeed(); //30
Car.accelerate();
Car.getSpeed(); //40;
Car.decelerate();
Car.decelerate();
Car.getSpeed(); //20
Car.getStatus(); // 'running';
Car.decelerate();
Car.decelerate();
Car.getStatus(); //'stop';
//Car.speed; //error

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var Car = function((){
var speed = 0;
function setSpeed(s){
speed = s //设置汽车起始速度
}
function getSpeed(){
return speed; //获取汽车的当前速度
}
function accelerate(){
speed += 10; //每次加速,提速10
}
function decelerate(){
speed -=10; //每次减速,减速10
}
function getStatus(){
if(0 < speed){
return "running" //如果速度大于0,状态为running
}
if(0 === speed){
return "stop" //如果速度等于0,状态为stop
}
if(0 > speed){
return "backing" //如果速度小于0,状态为backing
}
}
return { /*return一个对象,对象名是字符串,对象值是函数名*/
setSpeed: setSpeed,
getSpeed: getSpeed,
accelerate: accelerate,
decelerate: decelerate,
getStatus: getStatus,
}
})()
Car.setSpeed(30);
Car.getSpeed(); //30
Car.accelerate();
Car.getSpeed(); //40;
Car.decelerate();
Car.decelerate();
Car.getSpeed(); //20
Car.getStatus(); // 'running';
Car.decelerate();
Car.decelerate();
Car.getStatus(); //'stop';

定时器

  • JavaScript提供定时执行代码的功能,叫做定时器(timer);主要由setTimeout()和setInterval()这两个函数来完成它们向任务队列添加定时任务

setTimeout()

  • setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// setTimeout函数接受两个参数,第一个参数func|code是将要推迟执行的函数名或者一段代码,第二个参数delay是推迟执行的毫秒数
var timerId = setTimeout(func|code, delay);
// 输出结果就是1,3,2,因为setTimeout指定第二行语句推迟1000毫秒再执行
console.log(1);
setTimeout('console.log(2)',1000);
console.log(3);
// setTimeout方法一般总是采用函数名的形式
function f(){
console.log(2);
}
setTimeout(f,1000);
// 或者
setTimeout(function (){
console.log(2)
},1000);

setInterval()

  • setInterval函数的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 每隔1000毫秒就输出一个2
var timer = setInterval(function() {
console.log(2);
}, 1000);
// setInterval方法还可以接受更多的参数,每隔一段时间就执行一次
function f(){
console.log('Hello World')
}
setInterval(f, 1000);
// Hello World
// Hello World
// Hello World
// ...

clearTimeout()、clearInterval()

  • setTimeout和setInterval函数,都返回一个表示计数器编号的整数值,将该整数传入clearTimeout和clearInterval函数,就可以取消对应的定时器
1
2
3
4
5
var id1 = setTimeout(f,1000);
var id2 = setInterval(f,1000);
clearTimeout(id1);
clearInterval(id2);

运行机制

  • setTimeout和setInterval的运行机制是,将指定的代码移出本次执行;必须等到本轮 Event Loop 的所有任务都执行完,才会开始执行
  • 由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout和setInterval指定的任务,一定会按照预定时间执行
1
2
setTimeout(someTask, 100);
veryLongTask();
  • setTimeout,指定100毫秒以后运行一个任务。但是,如果后面的veryLongTask函数(同步任务)运行时间非常长,过了100毫秒还无法结束,那么被推迟运行的someTask就只有等着,等到veryLongTask运行结束,才轮到它执行
1
2
3
4
5
setInterval(function () {
console.log(2);
}, 1000);
sleep(3000);
  • 第一行语句要求每隔1000毫秒,就输出一个2。但是,紧接着的语句需要3000毫秒才能完成,那么setInterval就必须推迟到3000毫秒之后才开始生效

setTimeout(f, 0)

  • setTimeout(f, 0)的作用是,尽可能早地执行指定的任务。而并不是会立刻就执行这个任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// setTimeout(f, 0)必须要等到当前脚本的所有同步任务结束后才会执行
setTimeout(function() {
console.log("Timeout");
}, 0);
function a(x) {
console.log("a() 开始运行");
b(x);
console.log("a() 结束运行");
}
function b(y) {
console.log("b() 开始运行");
console.log("传入的值为" + y);
console.log("b() 结束运行");
}
console.log("当前任务开始");
a(42);
console.log("当前任务结束");
// 当前任务开始
// a() 开始运行
// b() 开始运行
// 传入的值为42
// b() 结束运行
// a() 结束运行
// 当前任务结束
// Timeout

code:这段代码输出结果是

explain

1
2
3
4
5
6
7
8
9
10
11
12
var a = 1;
setTimeout(function(){
a = 2;
console.log(a);
}, 0);
var a ;
console.log(a);
a = 3;
console.log(a);
/*1 3 2
因为setTimeout是异步执行的,执行顺序排在最后
*/

code:这段代码输出结果是

explain

1
2
3
4
5
6
7
8
9
10
11
12
var flag = true;
setTimeout(function(){
flag = false;
},0)
while(flag){}
console.log(flag);
/*进入一个无限循环,什么都不输出
因为setTimeout异步执行,执行顺序排到最后去,
所以while循环中的布尔值是true,会一直无限循环下去,
后面的js代码永远执行不到,所以没有输出
*/

code:下面这段代码输出?如何输出delayer: 0, delayer:1…(使用闭包来实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
for(var i = 0; i < 5; i++){
setTimeout(function(){
console.log('delayer:' + i);
}, 0);
console.log(i);
}
/*0 1 2 3 4
delayer:5
delayer:5
delayer:5
delayer:5
delayer:5
*/

explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//方法一:用闭包改写代码:
for(var i = 0; i < 5; i++){
setTimeout(function(i){
return function(){
console.log('delayer:' + i);
}
}(i), 0);
console.log(i);
}
/*0 1 2 3 4
delayer:0
delayer:1
delayer:2
delayer:3
delayer:4
*/
// 方法二:用let创建私有变量
for(let i = 0; i < 5; i++){
setTimeout(function(){
console.log('delayer:' + i);
}, 0);
console.log(i);
}
/*0 1 2 3 4
delayer:0
delayer:1
delayer:2
delayer:3
delayer:4
*/

BOM

  • BOM(Browser Object Model) 是指浏览器对象模型,是用于描述这种对象与对象之间层次关系的模型,浏览器对象模型提供了独立于内容的、可以与浏览器窗口进行互动的对象结构

元素的真实宽高

  • getComputedStyle方法接受一个HTML元素作为参数,返回一个包含该HTML元素的最终样式信息的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box {
width: 300px;
height: 200px;
margin: 0 auto;
border: 1px solid;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
var box = document.querySelector(".box");
window.getComputedStyle(box).width;// "300px"
//返回元素最终计算的宽度
window.getComputedStyle(box).height; // "200px"
//返回元素最终计算的高度
</script>
</body>
</html>

URL的编码/解码方法

  • 网页URL的合法字符分成两类,其他字符出现在URL之中都必须转义,规则是根据操作系统的默认编码,将每个字节转为百分号(%)加上两个大写的十六进制字母
1
2
URL元字符:分号(;),逗号(’,’),斜杠(/),问号(?),冒号(:),at(@),&,等号(=),加号(+),美元符号($),井号(#)
语义字符:a-z,A-Z,0-9,连词号(-),下划线(_),点(.),感叹号(!),波浪线(~),星号(*),单引号(\),圆括号(()`)

编码

  • encodeURI()
  • encodeURIComponent()
1
2
3
4
5
6
7
// encodeURI 方法的参数是一个字符串,代表整个URL。它会将元字符和语义字符之外的字符,都进行转义
encodeURI('http://www.example.com/q=春节')
// "http://www.example.com/q=%E6%98%A5%E8%8A%82"
// encodeURIComponent只转除了语义字符之外的字符,元字符也会被转义
encodeURIComponent('http://www.example.com/q=春节')
// "http%3A%2F%2Fwww.example.com%2Fq%3D%E6%98%A5%E8%8A%82"

解码

  • decodeURI()
  • decodeURIComponent()
1
2
3
4
5
6
7
8
// decodeURI用于还原转义后的URL。它是encodeURI方法的逆运算
decodeURI('http://www.example.com/q=%E6%98%A5%E8%8A%82')
// "http://www.example.com/q=春节"
// decodeURIComponent用于还原转义后的URL片段。它是encodeURIComponent方法的逆运算
decodeURIComponent('http%3A%2F%2Fwww.example.com%2Fq%3D%E6%98%A5%E8%8A%82')
// "http://www.example.com/q=春节"
  • 访问这个URL,用encodeURI()函数,把中文或其他的特殊字符编码,让浏览器能识别整段URL
  • 把这个URL放入到另一个URL中的时候,就要用 encodeURIComponent() 函数了,因为需要把 / = ? 这些特殊字符也编码,否则就容易出问题

判断用户的浏览器类型

1
2
3
4
5
6
7
8
9
10
11
12
function isAndroid(){
return /Android/i.test(navigator.userAgent)
}
function isIphone(){
return /iphone/i.test(navigator.userAgent)
}
function isIpad(){
return /ipad/i.test(navigator.userAgent)
}
function isIOS(){
return /ios/i.test(navigator.userAgent)
}

JavaScript的AJAX

  • AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML 通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新

XMLHttpRequest对象

  • XMLHttpRequest对象用来在浏览器与服务器之间传送数据

open方法

  • open(method,url,async),规定请求的类型、URL 以及是否异步处理请求。
  • method:请求的类型;GET 或 POST
  • url:文件所在服务器的路径
  • async:true(异步)或 false(同步)

同步和异步

  • 第三个参数是true是异步,false是同步
  • 当是异步时,浏览器把请求发送后就继续做自己的事,当onreadystatechange事件到来时说明服务端的数据来了,这时再处理数据。类似于一个节点绑定点击事件后就做后面的事,当用户点击了再执行绑定的处理函数
  • 当是同步时,JavaScript 会等到服务器响应就绪才继续执行。如果服务器繁忙或缓慢,应用程序会挂起或停止。 当使用 async=false 时,不用编写 onreadystatechange 函数,把代码放到 send() 语句后面即可(不推荐用)

send方法

  • send(string),将请求发送到服务器。
  • string:仅用于 POST 请求

XMLHttpRequest对象的属性

onreadystatechange

  • onreadystatechange属性指向一个回调函数,当readystatechange事件发生的时候,这个回调函数就会调用,并且XMLHttpRequest实例的readyState属性也会发生变化

readyState

  • readyState是一个只读属性,用一个整数和对应的常量,表示XMLHttpRequest请求当前所处的状态,一般来说,只研究4
  • 0: 请求未初始化
  • 1: 服务器连接已建立
  • 2: 请求已接收
  • 3: 请求处理中
  • 4: 请求已完成,且响应已就绪

status

  • status属性为只读属性,表示本次请求所得到的HTTP状态码,它是一个整数。一般来说,如果通信成功的话,这个状态码是200
  • 200, OK,访问正常
  • 301, Moved Permanently,永久移动
  • 302, Move temporarily,暂时移动
  • 304, Not Modified,未修改
  • 307, Temporary Redirect,暂时重定向
  • 401, Unauthorized,未授权
  • 403, Forbidden,禁止访问
  • 404, Not Found,未发现指定网址
  • 500, Internal Server Error,服务器发生错误

responseText

  • responseText属性返回从服务器接收到的字符串,该属性为只读
  • 如果服务器返回的数据格式是JSON,就可以使用responseText属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!doctype html>
<html>
<head>
</head>
<body>
<div class="query-area">
<input type="text" name="username" value="hunger" placeholder="hunger, ruoyu, anyone">
<button>查询朋友</button>
</div>
<div class="detail-area">
<ul>
</ul>
</div>
<script>
var btn = document.querySelector('.query-area button')
var input = document.querySelector('.query-area input')
btn.addEventListener('click', function(){
// 创建XMLHttpRequest对象
var xhr = new XMLHttpRequest()
// 增加onreadystatechange事件,以监听所属状态
xhr.onreadystatechange = function(){
// readyState等于4,加载完成并且状态码200加载成功或者状态码304未修改
if(xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)){
//将得到的JSON字符串转为JS能处理的数据
var friends = JSON.parse(xhr.responseText)
console.log(friends)
}
}
// 设置get请求,请求路径及异步
xhr.open('get', '/getFriends?username=' + input.value, true)
// 发送请求
xhr.send()
})
</script>
</body>
</html>

Ajax和form交互的区别

  • AJAX 是前端向后端发出数据请求,后端返回需要的数据,前端用这些数据改写HTML页面,页面不会刷新
  • form 是前端向后端发出数据请求,后端把需要的数据填入HTML模板(.ejs文件)创建新的HTML,再把新的HTML发回给前端,这样前端的页面就会做一次刷新

前后端开发联调及mock数据

前后端开发联调

  • 接口(路径)名称,统一命名,定制规范,拟定命名表(‘/getFriends’)
  • 接口请求方式(post/get)
  • 请求参数的名称、数量、对应值(‘ruoyu’、’hunger’)
  • 返回响应数据格式(json/text)

mock数据

  • 在 node.js 环境下安装 server-mock (npm install -g server-mock)
  • 在项目文件夹下,创建 router.js 文件,写好对应接口的响应函数,创造一些假数据
  • 启动server-mock:server start
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
router.get('/getFriends', function(req, res) {
var username = req.query.username // 通过 req.query获取请求参数
var friends
//根据请求参数mock数据
switch (username){
case 'ruoyu':
friends = ['小米', '小刚']
break
case 'hunger':
friends = ['小谷', '小花']
break;
default:
friends = ['没有朋友']
}
res.send(friends)
})

AJAX数据锁

  • 点击按钮,使用ajax获取数据,添加一个锁(true、false),防止用户在数据到来之前防止重复点击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var getData = document.getElementById('getData');
var lock = true; // 初始的数据锁是打开的
getData.addEventListener('click', function(){
if(!lock){
return; // 数据锁如果是锁上,这次点击就没用,退出函数;否则继续下面
}
lock = false; // //先把数据锁锁上,然后去发送请求
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if( xhr.status === 200 || xhr.status === 304){
console.log( JSON.parse(xhr.responseText) );
}
lock = true; // 数据到来后,才打开数据锁
}
}
xhr.open('get', '/getFriends?username=', true)
xhr.send();
});

code:封装一个ajax函数,能通过如下方式调用.后端在本地使用server-mock来mock数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function ajax(opts){
// todo ...
}
document.querySelector('#btn').addEventListener('click', function(){
ajax({
url: '/login', //接口地址
type: 'get', // 类型, post 或者 get,
data: {
username: 'xiaoming',
password: 'abcd1234'
},
success: function(ret){
console.log(ret); // {status: 0}
},
error: function(){
console.log('出错了')
}
})
});

效果图

images

explain

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<input type="text" id="user" placeholder="用户名">
<input type="text" id="pwd" placeholder="密码">
<button id="btn">登陆</button>
<script>
var btn = $("#btn");
var user = $("#user");
var pwd = $("#pwd");
btn.addEventListener('click', function() {
ajax({
url: '/login', //接口地址
type: 'get', // 类型, post 或者 get,
data: {
username: user.value,
password: pwd.value
},
success: function(ret) {
console.log(ret);
},
error: function() {
console.log('出错了');
}
})
});
function $(str) {
return document.querySelector(str);
}
function ajax(opts) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr .status === 304) {
var result = xhr.responseText;
opts.success(result);
}else{
opts.error();
}
}
}
// /login?username=xiaoming&password=abcd1234
var query = "?";
for(var key in opts.data){
query += key + "=" + opts.data[key] + "&";
}
query = query.slice(0,-1);
xhr.open(opts.type, opts.url + query, true);
xhr.send();
}
</script>
</body>
</html>

后端代码

1
2
3
4
5
6
7
8
router.get('/login', function(req, res) {
var username = req.query.username // 通过 req.query获取请求参数
var password = req.query.password
if (username === "xiaoming" && password === "abcd1234") {
res.send('登录成功')
}else{
res.send('用户名或密码错误')
}

code:ajax实现点击加载更多的功能,后端在本地使用server-mock来模拟数据

效果图

images

explain

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
ul,
li {
list-style: none;
padding: 0;
margin: 0;
}
#news > li {
width: 600px;
border: 1px solid #000;
border-radius: 5px;
font-size: 24px;
margin: 0 auto;
text-align: center;
padding: 5px;
margin-top: 10px;
}
.btn{
width: 200px;
display: block;
margin: 0 auto;
margin-top: 20px;
font-size: 20px;
padding: 10px;
background-color: #ddd;
}
</style>
</head>
<body>
<ul id="news">
<li>新闻1</li>
<li>新闻2</li>
<li>新闻3</li>
<li>新闻4</li>
<li>新闻5</li>
</ul>
<button id="more" class="btn">加载更多</button>
<script>
var btn = $("#more");
var newList = $("#news");
var dataLock = true; //数据锁初始打开
btn.addEventListener('click', function() {
if (!dataLock) {
return; //如果正在请求数据,那这次点击什么都不做
}
dataLock = false;
ajax({
url: '/loadMore', //接口地址
type: 'post', // 类型, post 或者 get,
data: {
index: newList.children.length,
needPage: 3
},
success: function (ret){
addNews(ret);
dataLock = true; //后端响应数据完,打开数据锁
},
error: function() {
console.log('出错了');
}
})
});
function $(str) {
return document.querySelector(str);
}
function addNews(arr) {
var frag = document.createDocumentFragment();
for (var i = 0; i < arr.length; i++) {
var list = document.createElement('li');
list.innerText = arr[i];
frag.appendChild(list);
}
newList.appendChild(frag);
}
function ajax(opts) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr .status === 304) {
var result = JSON.parse(xhr.responseText);
opts.success(result);
}else{
opts.error();
}
}
}
// /loadMore?index=5&needPage=3
var query = '';
for(var key in opts.data){
query += key + '=' + opts.data[key] + '&';
}
if (opts.type === 'post') {
xhr.open(opts.type,opts.url,true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(query);
}else if(opts.type === 'get'){
xhr.open(opts.type, opts.url + '?' + query, true);
xhr.send();
}
}
</script>
</body>
</html>

后端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
router.get('/loadMore', function(req, res) {
var idx = req.query.index; // 通过 req.query获取请求参数
var ndpg = req.query.needPage;
var news = [];
for (var i = 0; i < ndpg; i++) {
news.push('新闻' + (parseInt(idx) + i + 1));
}
res.send(news);
})
router.post('/loadMore', function(req, res) {
var idx = req.body.index; // 通过 req.body获取请求参数
var ndpg = req.body.needPage;
var news = [];
for (var i = 0; i < ndpg; i++) {
news.push('新闻' + (parseInt(idx) + i + 1));
}
res.send(news);
})

跨域

  • 跨域就是不同域下的接口交互

同源策略(Same origin Policy)

  • 浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div class="container">
<ul class="news">
<li>第11日前瞻:中国冲击4金 博尔特再战200米羽球</li>
<li>最“出柜”奥运?同性之爱闪耀里约</li>
<li>中英上演奥运金牌大战</li>
</ul>
<button class="change">换一组</button>
</div>
<script>
$('.change').addEventListener('click', function(){ //给元素绑定事件
var xhr = new XMLHttpRequest();
xhr.open('get','http://b.xiaoming.com:8080/getNews',true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 304) {
appendHtml(JSON.parse(xhr.responseText));
}
}
}
})
function appendHtml(news){
var html = '';
for( var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>';
}
console.log(html);
$('.news').innerHTML = html;
}
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>

后端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
router.get('/getNews', function(req, res) {
var news = [
"第11日前瞻:中国冲击4金 博尔特再战200米羽球",
"正直播柴飚/洪炜出战 男双力争会师决赛",
"女排将死磕巴西!郎平安排男陪练模仿对方核心",
"没有中国选手和巨星的110米栏 我们还看吗?",
"中英上演奥运金牌大战",
"博彩赔率挺中国夺回第二纽约时报:中国因对手服禁药而丢失的奖牌最多",
"最“出柜”奥运?同性之爱闪耀里约",
"下跪拜谢与洪荒之力一样 都是真情流露"
]
var data = [];
for (var i = 0; i < 3; i++) {
var index = parseInt(Math.random() * news.length);
data.push(news[index]);
news.splice(index, 1);
}
res.send(data);
})

效果图

images

跨域的实现方式

  • JSONP
  • CORS
  • 降域
  • postMessage

JSONP

  • 定义数据处理函数_fun
  • 创建script标签,src的地址执行后端接口,最后加个参数callback=_fun
  • 服务端在收到请求后,解析参数,计算返还数据,输出 fun(data) 字符串
  • fun(data)会放到script标签做为js执行。此时会调用fun函数,将data做为参数
前端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div class="container">
<ul class="news">
<li>第11日前瞻:中国冲击4金 博尔特再战200米羽球</li>
<li>最“出柜”奥运?同性之爱闪耀里约</li>
<li>中英上演奥运金牌大战</li>
</ul>
<button class="change">换一组</button>
</div>
<script>
$('.change').addEventListener('click', function(){ //给元素绑定事件
var script = document.createElement('script'); //点击事件后,创造一个script元素
script.src = 'http://b.xiaoming.com:8080/getNews?callback=appendHtml';
/*设置script元素的src属性,?前面的是协议,域名,接口。
?后面是用户传递的一个callback参数
*/
document.head.appendChild(script); //把script元素添加到页面上
})
function appendHtml(news){ // 声明包裹数据的函数
var html = '';
for( var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>';
}
console.log(html);
$('.news').innerHTML = html;
}
function $(id){
return document.querySelector(id);
}
</script>
</script>
</body>
</html>
后端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
router.get('/getNews', function(req, res) {
var news = [
"第11日前瞻:中国冲击4金 博尔特再战200米羽球",
"正直播柴飚/洪炜出战 男双力争会师决赛",
"女排将死磕巴西!郎平安排男陪练模仿对方核心",
"没有中国选手和巨星的110米栏 我们还看吗?",
"中英上演奥运金牌大战",
"博彩赔率挺中国夺回第二纽约时报:中国因对手服禁药而丢失的奖牌最多",
"最“出柜”奥运?同性之爱闪耀里约",
"下跪拜谢与洪荒之力一样 都是真情流露"
]
var data = [];
for (var i = 0; i < 3; i++) {
var index = parseInt(Math.random() * news.length);
data.push(news[index]);
news.splice(index, 1);
}
var cb = req.query.callback; //获取callback参数内的值
if (cb) {
res.send(cb + '(' + JSON.stringify(data) + ')');
//如果callback参数的值存在,就用它做函数名包裹数据,再发送
} else {
res.send(data);
//如果没有callback参数,数据就直接发送
}
})
  • jsonp就是获取一段代码,用js去执行,前端向后端发送一个参数,后端用这个参数封装数据,发回来,前端再执行
  • jsonp需要前后端配合,后端同意才能跨域,没有安全问题
效果图

images

CORS

  • 全称为 Cross Origin Resource Sharing,跨域资源共享,是一种ajax跨域请求资源的方式,支持现代浏览器,IE支持10以上
  • 在后端的响应头添加一个Access-Control-Allow-Origin属性,属性的值是允许的域名。它的好处是前端的请求就是ajax,不需要修改,只要后端开访问权限即可,很方便
前端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div class="container">
<ul class="news">
<li>第11日前瞻:中国冲击4金 博尔特再战200米羽球</li>
<li>最“出柜”奥运?同性之爱闪耀里约</li>
<li>中英上演奥运金牌大战</li>
</ul>
<button class="change">换一组</button>
</div>
<script>
$('.change').addEventListener('click', function(){ //给元素绑定事件
var xhr = new XMLHttpRequest();
xhr.open('get','http://b.xiaoming.com:8080/getNews',true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 304) {
appendHtml(JSON.parse(xhr.responseText));
}
}
}
})
function appendHtml(news){
var html = '';
for( var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>';
}
console.log(html);
$('.news').innerHTML = html;
}
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>
后端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
router.get('/getNews', function(req, res) {
var news = [
"第11日前瞻:中国冲击4金 博尔特再战200米羽球",
"正直播柴飚/洪炜出战 男双力争会师决赛",
"女排将死磕巴西!郎平安排男陪练模仿对方核心",
"没有中国选手和巨星的110米栏 我们还看吗?",
"中英上演奥运金牌大战",
"博彩赔率挺中国夺回第二纽约时报:中国因对手服禁药而丢失的奖牌最多",
"最“出柜”奥运?同性之爱闪耀里约",
"下跪拜谢与洪荒之力一样 都是真情流露"
]
var data = [];
for (var i = 0; i < 3; i++) {
var index = parseInt(Math.random() * news.length);
data.push(news[index]);
news.splice(index, 1);
}
res.header("Access-Control-Allow-Origin", "*");
/*后端如果允许访问数据,就在响应头中添加一个参数Access-Control-Allow-Origin,
后面的参数值为允许的域名,这里 * 表示允许所有人访问,如果只想允许个别人访问,就单独设置
*/
res.send(data);
})
效果图

images

降域

  • 页面ifram内部嵌套一个网页,如果域名不同,就不能互相操作,因有跨域问题,如果域名的后缀是一样的,只是前面不一样,可以用降域来解决
前端index代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!doctype html>
<html lang="en">
<style>
.ct{
width: 910px;
margin: auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
<div class="ct">
<h1>使用降域实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.xiaoming.com:8080/index.html">
</div>
<iframe src="http://b.xiaoming.com:8080/index1.html" frameborder="0" ></iframe>
</div>
<script>
//URL: http://a.xiaoming.com:8080/index.html
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "xiaoming.com"
</script>
</html>
前端index1代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!doctype html>
<html>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
<input id="input" type="text" placeholder="http://b.xiaoming:8080/index1.html">
<script>
// URL: http://b.xiaoming.com:8080/index1.html
document.querySelector('#input').addEventListener('input', function(){
window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'xiaoming.com';
</script>
</html>

images

postMessage

  • postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递
  • postMessage(data,origin)方法接受两个参数1.data:要传递的数据2.origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL]

前端index代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.ct{
width: 910px;
margin: auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
</head>
<body>
<div class="ct">
<h1>使用postMessage实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.xiaoming.com:8080/index.html">
</div>
<iframe src="http://b.xiaoming.com:8080/index1.html" frameborder="0" ></iframe>
</div>
<script>
//URL: http://a.xiaoming.com:8080/index.html
$('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].postMessage(this.value,'*');
})
window.addEventListener('message',function(e) {
$('.main input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>
前端index1代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
</head>
<body>
<input id="input" type="text" placeholder="http://b.xiaoming.com:8080/index1.html">
<script>
// URL: http://b.xiaoming.com:8080/index1.html
$('#input').addEventListener('input', function(){
window.parent.postMessage(this.value, '*');
})
window.addEventListener('message',function(e) {
$('#input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>

images