目录
一,Javascript介绍以及起源
二,Javascript的语法
三,标识符和关键字
四,js的数据类型
五,运算符
六、数据类型间转换
七,流程控制
八,JsDOM操作
一,获取节点
二,创建插入节点
九,数组和对象
十,正则表达式
扩展:
js函数
let和const和var
this
闭包
事件冒泡和事件捕获
回调函数
常用事件
事件的绑定
定时器
一,Javascript介绍以及起源
- js一种直译脚本语言,一种动态语言,弱类型语言,支持内置类型。它的解释器被称为javascript引擎。
- 为什么会命名为JavaScript呢?原因是在当时,Java非常火,网景公司希望借用Java在当时的名气来进行推广。其实事实上呢,JavaScript除了语法上有点像Java外,别的地方都跟Java没有任何关系。
- JavaScript和ECMAScript的关系:从上面讲的JavaScript的由来中,我们就知道JavaScript由网景公司的布兰登·艾奇开发出来的,一年后,微软又模仿JavaScript开发出了一种编程语言叫JScript,再后来,陆续又有别的商家推出JavaScript的不同实现语言。这就导致JavaScript的语法和特性日益混乱,其标准化问题被提上日程。最终由欧洲计算机制造商协会(ECMA)以JavaScript1.1为蓝本,制定了【ECMA-262】标准,并由此标准定义了一种新脚本语言ECMAScript。随后,ISO也采用ECMAScript作为标准,各浏览器厂商便纷纷开始将ECMAScript作为各自JavaScript实现的基础。ECMAScript是一种语言标准,JavaScript是对ECMAScript的一种实现。
- js用途
- 基于nodeJs技术进行服务端的编程(浏览器是客户端的JavaScript运行环境,相对的,nodejs就是服务端的JavaScript运行环境。)
二,Javascript的语法
- js可以有几种写法
<script>
document.write("<h1>这是一个标题</h1>");
</script>
可位于 HTML 的 <body> 或 <head> 部分中,或者同时存在于两个部分中
(通常的做法是把函数放入 <head> 部分中,或者放在页面底部。这样就可以把它们安置到同一处位置,不会干扰页面的内容)
<!DOCTYPE html>
<html>
<body>
<script src="myScript.js"></script>
</body>
</html>
(你可以将脚本放置于 <head> 或者 <body>中,放在 <script> 标签中的脚本与外部引用的脚本运行效果完全一致)
<button onclick="(function(){alert(1)//这里可以写任何逻辑代码})()"
- js加载顺序
- 放在<head>标签中:浏览器解析HTML,发现script标签时,会先下载完所有这些script,再往下解析其他的HTML。浏览器在下载JavaScript时,是不能多个JavaScript并发一起下载的,不管JavaScript是不来来自同一个host,浏览器最多只能同时下载两个JavaScript,且浏览器下载JavaScript时,就block掉解析其他HTML的工作。所以将script放在头部,会让网页内容呈现滞后,导致用户感觉到卡,建议将script放在尾部,这样能加速网页加载。
- 放在<body>标签后面:是浏览器只能先解析完整个HTML页面,再下载JavaScript。而对于一些高度依赖于JavaScript的网页,就会显得慢了。
- 最优方案:把<script>放在头部,使用async和defer属性。80%的现代浏览器都认识async和defer属性,这两个属性能让浏览器做到一边下载JavaScript(还是只能同时下载两个JavaScript),一边解析HTML,它的优点不是增加JavaScript的并发下载数 量,而是做到下载时不block解析HTML。
<script type="text/javascript" src="path/to/script1.js" async></script>
<script type="text/javascript" src="path/to/script2.js" async></script>
- 带async属性的script会异步执行,只要下载完就执行,这会导致script2.js可能先于 script1.js执行(如果script2.js比较大,下载慢)。defer属性就能保证script有序执行,script1.js先执行,script2.js后执行。
三,标识符和关键字
- 什么是标识符
- JavaScript 中不能使用连字符。它是为减法预留的。
first-name, last-name, master-card, inter-city.
这是错误的
下划线
first_name, last_name, master_card, inter_city.
这是正确的
驼峰式大小写
FirstName, LastName, MasterCard, InterCity.
firstName, lastName, masterCard, interCity
这两种都是正确的
- 关键字
关键字是指js语言中有特定含义,称为js语法中的一部分
例如:var let const for if foreach break continue do whil switch..
- 保留字
未来某个js版本会称为关键字的单词,一样是不可以当成变量名或者方法名来使用的
- 注释
// 单行注释
/**/ 多行注释
四,js的数据类型
- 数据类型
基本数据类型:
Number String Boolean Null Undefined Symbol(ES6)
应用数据类型:
Object(在js中除了基本数据类型以外的都是对象,数组是对象,函数是对象,正则表达式是对象)
- 数字类型(Number)
- NaN与所有的数值都不相等,包括自己。判断是否为NaN的方法是——isNaN();,例如对变量“a”做判断,isNaN(a);
- 尽量避免使用浮点数进行运算,因为存在精度问题。例如:console.log((1/3) === (1-20/3));打印结果为false。这种情况通常用相似度判断:console.log(Math.abs(1/3-(1-2/3))<0.0000000000001);
123 // 整数123
123.1 // 浮点数123.1
1.23e3 // 科学计数法
-99 // 负数
NaN // not a number,NaN与所有的数值都不相等,包括自己
Infinity // 表示无限大
- 字符串型(string)
字符串是存储字符的变量,用来表示文本的数据类型,程序中的字符串是包含单引号/双引号,由单引号来界定我双引号
//正常字符串使用单引号或者双引号包裹
'abc'
"abc"
//注意转义字符
\'
\n
\t
\u#### Unicode字符 \u4e2d
\x41
//字符串基本操作
let str = 'student';
str.length; // 获取字符串长度
str[0]; // 通过索引直接获取字符串对应位置字符
str.toUpperCase(); // 转大写
str.toLowerCase(); // 转小写
str.indexOf('m'); // 获取m在字符串str中第一次出现的索引
str.substring(n); // 从第n个索引开始截取之后的所有字符 [n)
str.substring(n,m); // 截取第n个索引到第m个索引之间的所有字符 [n,m)
str[0] = 'm'; //结果仍为原来的值
- 布尔类型(boolean)
true和false ,这两个值一般用于说明某个事物是真或者家
js一般用布尔类型来比较所得到的结果
- null(空)
- undefined(未定义)
这个值表示变量不含有值,没有定义的值,或者被定义了一个不存在的属性值
- null和undefined区别
null它表示一个变量被赋予一个空值,而undefined是表示变量还没有被赋值
五,运算符
用于执行程序代码运算,会针对一个以上操作数来进行
- 算术运算符+ - * / % ++ --
(字符串拼接使用“+”)
- 比较运算符 < > == != <= >=
= // 赋值
== // 等于(类型不一样,值一样,也会判断为true)
=== // 绝对等于(类型相同值相等才会为true)
!== //不全等于:将数值以及数据类型一并比较
- 赋值运算符= += -= *= /= %=
- 逻辑运算符
&& 全真为真
|| 一个为真就是真
! 取反
- 类型运算符
typeof 返回变量的类型
instanceof 返回 true,如果对象是对象类型的实例
- 三元运算符(三目运算符)
表达式1?表达式2:表达式3
当表达式1成立时 执行表达式2 否则执行表达式3
六、数据类型间转换
- 显示数据类型转换
Number():可以将任意类型的参数转换为数字类型,遵循以下规则:
- 如果它是一个布尔值true和false将被分别转成1和0;
- 如果它是以个数字,返回它本身
- 如果是null,返回0;
- 如果是undefined,返回NaN
- 如果是一个字符串:
如果这个字符串只包含数字,则直接将它转成10进制数字(忽略前面的0)
如果有有效浮点格式,将它转成一个浮点数值
如果是空字符串,转换为0
如果以上都不符合==>NaN
parseInt(string,num):可以解析一个字符串,返回一个整数,遵循以下规则:
- 忽略字符串前面所有的空格,直到找到第一个非空字符为止
- 如果第一个 字符不是数字或者“-” 直接返回NaN、
- 如果第一个符是数字,它解析到遇到的第一个不是数字的字符为止
- 如果上面解析完结果是以0开头,就将它当成一个八进制来解析
- 如果以0xx开头,则当成十六进制来解析。
- 如果我指定了num参数,那么 它就以num进制来解析
String():将任意的一个类型的值转换为字符串,遵循一下下规则:
- 如果是null,==>"null"
- 如果是undefined ==>"undefined"
toFied(num):可以把Number类型四舍五入为指定小数位的字符串,返回的字符串,num保留小数位
Boolean():如果这个值是空字符串("")、数字零(0)、undefined或者null 会返回false,否则返回true,空格并不是空字符串
- 隐式数据类型转换
js中的数据类型是非常弱的,在使用算数运算符时,运算符两边的数据类型可以是任意的,比如,一个字符串可以和一个数字相加。之所以不同的数据类型之间可以做运算,是因为js引擎在运算之前会悄悄地把他们进行了隐式类型转换。
主要涉及到三种转换
1、将值转为原始值,ToPrimitive()。
2、将值转为数字,ToNumber()。
3、将值转为字符串,ToString()。
七,流程控制
- if、while、for等语句和Java无区别。
- forEach
对数组的每个元素执行一次提供的函数。
let arr = [1,8,5,3,2,5,6,2,21,2,5];
arr.forEach(function(value){
console.log(value);
});
- for in
该种循环不同于Java的增强型for循环,Java增强型的for循环中的引号在此换成了in,而且遍历出来的结果并非数组元素,而是元素索引。
JavaScript不仅可以遍历数组,还可以遍历对象
let arr = [1,8,5,3,2,5,6,2,21,2,5];
for(let num in arr){
console.log(num); // 每个元素的索引
console.log(arr[num]); // 元素值遍历
}
let obj = {
name:'豆腐干',
age:'45',
sex:'可'
};
for(let el in obj){
console.log(obj[el]); // 遍历输出对象属性
}
- for of (iteratot)
ES6新特性
此种遍历方法不同于for in,遍历出来的结果为数组的元素,而且for of不仅可以遍历数组,还可以遍历Set和Map(注意,不能用于对象)
let arr = [1,2,3,4,5,6];
let map = new Map([['苹果',2],['菠萝',5],['橘子',6],['樱桃',12]]);
let set = new Set(['a','b','c','d','e','f']);
for(let a of arr){
console.log(a); // 遍历输出数组元素
}
for(let b of map){
console.log(b); // 遍历输出Map集合元素
}
for(let c of set){
console.log(c); // 遍历输出Set集合元素
}
- for in存在的Bug
for in 循环,在数组新增属性后(数组长度是不变的),遍历下标会把新增属性的key值遍历出来(这是一个bug)。
let arr = [1,2,3];
console.log("数组长度"+arr.length); // 数组长度3
for(let a in arr){
console.log("索引:"+a); // 0 1 2
}
for(let a of arr){
console.log("元素值"+a); // 1 2 3
}
arr.age = 99;
console.log("数组长度"+arr.length); // 数组长度3
for(let a in arr){
console.log("索引:"+a); // 0 1 2 age
}
for(let a of arr){
console.log("元素值"+a); // 1 2 3
}
console.log(arr["age"]); // 99
- Map
// ES6之前不支持
let map = new Map();
本质就是key : value键值对
Map的get和set方法
let family = new Map([['我',90],['小',60]]);
console.log(family.get('我')); // 90
console.log(family.get('小')); // 60
family.set('千层',100); // 新增或修改元素
console.log(family.get('千层')); // 查询元素
console.log(family);
family.delete("千层"); // 删除元素
console.log(family);
- Se
// ES6之前不支持
let set = new Set();
无序不重复集合
Set的add和delete方法:
let set = new Set([3,1,1,1,2]);
// set会自动去重
console.log(set); // Set(3) {3, 1, 2}
set.add('jh'); // 添加
console.log(set);
set.delete('jh'); // 删除
console.log(set);
console.log(set.has(3)); // 判断是否拥有3这个元素
八,JsDOM操作
一,获取节点
- 通过id获取
document.getElementById("id")
节点.getElementById("id值")
返回的是一个具体的节点
- 通过标签名来获取节点
getElementsByTagName("div")
返回的是一个节点数组,即使只有一个
- 通过标签的Name值来获取
getElementsByName("标签的name值")
返回的是一个节点数组
- 通过class值来获取节点
getElementsByClassName("类名")
返回的是一个节点数组
- 通过选择器
querySelector('选择器')//根据我选择器的结果集返回第一个
querySelectorAll('选择器')//根据我选择器的结果集返回一个数组
getElementsByClassName在IE9以下无效的
- 获取节点
获取节点.parentNode-->获取到节点的父节点
获取节点.children-->获取到节点的子节点集合
获取节点.childNodes-->获取到节点的子节点集合(带有前后两个空白的文本节点)
二,创建插入节点
- 创建节点
document.createElement("div")//创建一个元素节点
document.createTextNode("文本文本")//创建一个文本节点
被插入的节点.appendChild(创建的节点)//在节点后面添加
父节点.insertBefore(创建的节点,被插入的节点)//在已知父节点的某个孩子前面添加内容
- 改变文本内容
选中的元素.innerText='';//直接将HTML代码当做字符来处理
选中的元素.innerHTML='';//可以识别HTML代码
删除:直接设置为空("")
- 替换节点
父节点.replaceChild(新节点,老节点)
- 复制节点
选中的元素.cloneNode(true/false):
当clone参数为true的时候:选中元素里面所有都得内容复制
当clone参数为false的时候:选中元素本身复制
- 删除节点
父节点.removeChild(子节点)
- 节点属性操作
//如何来获取属性:
选中的元素.getAttribute("属性名")
//更改属性:
选中的元素.setAttribute("属性名","新的属性值")
//新增属性
选中的元素.setAttribute("原本没有的属性名","属性值")
//删除属性
选中的元素.removeAttribute("属性名");
九,数组和对象
- 如何来定义对象
- 语法
var obj = {};
- 使用new关键字来创建
var obj = new Object();//创建一个空对象
var arr = new Array();//创建一个空的数组对象
var time = new Date();//创建一个初始化的日期对象
- 通过构造函数的形式来创建对象
var obj =new Test();
function Test(num1,num2){
this.number1=num1;
this.number2=num2;
}
var obj =Object.create(null);
var obj =Object.create({"name":"tom","age":"3"});
- 对象的属性
//对象属性
对象.属性名 = 属性值
//对象属性值可以是任何一种js的数据类型 包括对象
//获取对象的属性
对象.属性名
对象[属性名]
- 遍历对象(for in循环)
for(var 变量 in 对象){
// 属性名:变量
// 属性值: 对象[变量]
}
- Date对象
Date对象用于处理日期何时间
//Date 对象用于处理日期和时间
var myDate=new Date()
//Date 对象会自动把当前日期和时间保存为其初始值。
- Date对象属性
返回对创建此对象的 Date 函数的引用。
var test=new Date();
if (test.constructor==Date)
{
document.write("This is a Date");
}
//输出
This is a Date
prototype 属性使您有能力向对象添加属性和方法。
function employee(name,job,born)
{
this.name=name;
this.job=job;
this.born=born;
}
var bill=new employee("Bill Gates","Engineer",1985);
employee.prototype.salary=null;
bill.salary=20000;
document.write(bill.salary);
- Date对象方法
Date(); //返回当日的日期和时间。
getDate();//从 Date 对象返回一个月中的某一天 (1 ~ 31)。
getDay();//从 Date 对象返回一周中的某一天 (0 ~ 6)。
getTime();//返回 1970 年 1 月 1 日至今的毫秒数。
setDate();//设置 Date 对象中月的某一天 (1 ~ 31)。
toString();//把 Date 对象转换为字符串。
- 对象的常见用法
属性通过逗号隔开,最后一个属性无需加逗号。
使用一个不存在的属性不会报错,只会提示 undefined。
let person = {
name:'SpenceDou',
age:20,
score:150
}
console.log(person.sex); // undefined
JavaScript对象可以动态删减、添加属性
let person = {
name:'SpenceDou',
age:20,
score:150
}
delete person.name; // true
console.log(person); // {age: 20, score: 150}
person.sex = "男";
console.log(person); // {age: 20, score: 150, sex: "男"}
JavaScript可以判断属性值是否在这个对象中。xxx in xxx;
let person = {
name:'SpenceDou',
age:20,
score:150
}
'age' in person; // true
'toString' in person; // true 继承,父类中拥有
person.hasOwnProperty('toString'); // false 判断是否自身拥有
JavaScript中的所有键都是字符串,任意值都是object。
let person = {
name:'SpenceDou',
age:20,
score:150
}
person['age'] // 20
- 数组
- 数组内可以存放任意数据类型的数据(本质上它也是对象)
- 数组元素不赋值的情况下 值为undefined
- 如果数组打印的时候,元素不赋值""
- 访问数组范围之外的元素,不会出现越界的问题,undefined
- 定义数组大小,照样可以添加更多元素
- 定义数组的方法
var arr=[]//定义一个空数组
var arr=
[
10,
20,
{"name":"tomy","age":19},
0.1,
"string",true,
["aaa","bbb"]
]
//定义的同时赋值
var arr=new Array();//定义一个空数组
var arr = new Array(
10,
20,
{"name":"tomy","age":19},
0.1,
"string",
true,
["aaa","bbb"]
)
//定义的同时赋值
var arr=new Array(10)//定义一个长度为10的数组
- 改变和赋予值
数组名[下标] = 值;
数组名[下标] = 值;
- 数组分类
索引数组:下标是数字
关联数组:下标是可以是自定义的字符
- 二维数组
//二维数组:数组里面的元素还是数组
var arr = [["id","aaa",10],[1,1,2,3],[1,1,1]]
- 数组中常用的方法
let arr = [1,2,3,4,5,'1','2',"1","3"];
// indexOf():获取字符元素第一次出现的索引,字符串的 "1" 和数字 1 是不同的
console.log(arr.indexOf(1)); // 0
console.log(arr.indexOf('1')); // 5
console.log(arr.indexOf("1")); // 5
// slice()截取Array的一部分,返回一个新数组。类似于字符串的 substring() 截取区间也为左闭右开
let arr2 = arr.slice(3);
let arr3 = arr.slice(1,5);
// push():向尾部压入元素
arr.push('a',"b")
console.log(arr); // (11) [1, 2, 3, 4, 5, "1", "2", "1", "3", "a", "b"]
// pop:弹出尾部最后一个元素
arr.pop();
console.log(arr); // (10) [1, 2, 3, 4, 5, "1", "2", "1", "3", "a"]
// unshift():向头部压入元素
arr.unshift("a","b");
console.log(arr); // (12) ["a", "b", 1, 2, 3, 4, 5, "1", "2", "1", "3", "a"]
// shift():弹出头部第一个元素
arr.shift();
console.log(arr); // (11) ["b", 1, 2, 3, 4, 5, "1", "2", "1", "3", "a"]
// sort():排序
arr.sort(); // (11) [1, "1", "1", 2, "2", 3, "3", 4, 5, "a", "b"]
// reverse():顺序反转
arr.reverse(); // (11) ["b", "a", 5, 4, "3", 3, "2", 2, "1", "1", 1]
// concat():拼接数组(返回的是新的字符串,并未覆盖原来的)
arr.concat([8,9,10]); // (14) ["b", "a", 5, 4, "3", 3, "2", 2, "1", "1", 1, 8, 9, 10]
// join():打印拼接数组,使用特定的字符串连接
arr.join('-'); // "b-a-5-4-3-3-2-2-1-1-1"
- 数组迭代
every();
//对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。
some();
//对数组的每一项运行给定函数,如果该函数对任一项返回true,则返回true。
var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
var everyResult = numbers.every(function(item, index, array){
return (item > 3);
})
console.log(everyResult); //false
var someResult = numbers.some(function(item, index, array) {
return (item > 3);
})
console.log(someResult); //true
filter();
//对数组中的每一项运行给定函数,返回改函数会返回true
var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
var filterResult = numbers.filter(function(item, index, array) {
return (item > 3);
})
console.log(filterResult); //[4, 5, 4]
map();
//对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
var mapResult = numbers.map(function(item, index, array) {
return item * 2;
})
console.log(mapResult); //[2, 4, 6, 8, 10, 8, 6, 4, 2]
forEach();
//对数组中的每一项运行给定的函数,该方法没有返回值,本质上与使用for循环迭代数组一样。
var numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.forEach(function(item, index, array) {
//执行某些操作
})
十,正则表达式
- 元字符
根据正则表达式语法规则,大部分字符仅能够描述自身,这些字符被称为普通字符,如所有的字母、数字等。
元字符就是拥有特动功能的特殊字符,大部分需要加反斜杠进行标识,以便于普通字符进行区别,而少数元字符,需要加反斜杠,以便转译为普通字符使用。JavaScript 正则表达式支持的元字符如表所示。
- 描述字符范围
在正则表达式语法中,方括号表示字符范围。在方括号中可以包含多个字符,表示匹配其中任意一个字符。如果多个字符的编码顺序是连续的,可以仅指定开头和结尾字符,省略中间字符,仅使用连字符-表示。如果在方括号内添加脱字符^前缀,还可以表示范围之外的字符。例如:
- [abc]:查找方括号内任意一个字符。
- [^abc]:查找不在方括号内的字符。
- [0-9]:查找从 0 至 9 范围内的数字,即查找数字。
- [a-z]:查找从小写 a 到小写 z 范围内的字符,即查找小写字母。
- [A-Z]:查找从大写 A 到大写 Z 范围内的字符,即查找大写字母。
- [A-z]:查找从大写 A 到小写 z 范围内的字符,即所有大小写的字母
- 选择匹配
选择匹配类似于 JavaScript 的逻辑与运算,使用竖线|描述,表示在两个子模式的匹配结果中任选一个
//匹配任意数字或字母
var r = /\w+|\d+/;
- 重复匹配
在正则表达式语法中,定义了一组重复类量词,如表所示。它们定义了重复匹配字符的确数或约数。
var s = "ggle gogle google gooogle goooogle gooooogle goooooogle gooooooogle goooooooogle";
//如果仅匹配单词 ggle 和 gogle,可以设计:
var r = /go?gle/g;
//如果匹配第 4 个单词 gooogle,可以设计:
var r = /go{3}gle/g;
//如果匹配第 4 个到第 6 个之间的单词,可以设计:
var r = /go{3,5}gle/g;
//量词*表示前面字符或表达式可以不出现,或者重复出现任意多次。等效于:
var r = /go(0,)gle/g;
//量词+表示前面字符或子表达式至少出现 1 次,最多重复次数不限。等效于:
var r = /go{1,}gle/g;
- 边界量词
边界就是确定匹配模式的位置,如字符串的头部或尾部,具体说明如表所示。
var s = "how are you"
//匹配最后一个单词
var r = /\w+$/;
//匹配第一个单词
var r = /^\w+/;
//匹配每一个单词
var r = /\w+/g;
正则详情:http://http://c.biancheng.net/view/5632.html
- 正则的方法
search() 方法 用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串,并返回子串的起始位置。
replace() 方法 用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
var str = "Visit Runoob!";
var n = str.search(/Runoob/i);
var str1=str.replace("Runoob","mocrosoft")
将Runoob换为mocrosoft
输出结果:6
/runoob/i 是一个正则表达式。
runoob 是一个正则表达式主体 (用于检索)。
i是一个修饰符(搜索时不分大小写)
test() 方法是一个正则表达式方法。
test() 方法用于检测一个字符串是否匹配某个模式,如果字符串中含有匹配的文本,则返回 true,否则返回 false。
扩展:
js函数
- 作用域
作用域是可访问变量的集合。
在 JavaScript 中, 作用域为可访问变量,对象,函数的集合。
JavaScript 函数作用域: 作用域在函数内修改。
- 局部作用域
变量在函数内声明,变量为局部作用域。
局部变量:只能在函数内部访问。
// 此处不能调用 carName 变量
function myFunction() {
var carName = "Volvo";
// 函数内可调用 carName 变量
}
因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。
局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
- 全局变量
变量在函数外定义,即为全局变量。
全局变量有全局作用域: 网页中所有脚本和函数均可使用。
var carName = " Volvo";
// 此处可调用 carName 变量
function myFunction() {
// 函数内可调用 carName 变量
}
如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。
以下实例中 carName 在函数内,但是为全局变量。
// 此处可调用 carName 变量
function myFunction() {
carName = "Volvo";
// 此处可调用 carName 变量
}
- JavaScript 变量生命周期
JavaScript 变量生命周期在它声明时初始化。
局部变量在函数执行完毕后销毁。
全局变量在页面关闭后销毁。
- HTML 中的全局变量
在 HTML 中, 全局变量是 window 对象: 所有数据变量都属于 window 对象。
//此处可使用 window.carName
function myFunction() {
carName = "Volvo";
}
let和const和var
- JavaScript 块级作用域(Block Scope)
使用 var 关键字声明的变量不具备块级作用域的特性,它在 {} 外依然能被访问到。
{
var x = 2;
}
// 这里可以使用 x 变量
在 ES6 之前,是没有块级作用域的概念的。
ES6 可以使用 let 关键字来实现块级作用域。
let 声明的变量只在 let 命令所在的代码块 {} 内有效,在 {} 之外不能访问。
{
let x = 2;
}
// 这里不能使用 x 变量
- 重新定义变量
使用 var 关键字重新声明变量可能会带来问题。
在块中重新声明变量也会重新声明块外的变量:
var x = 10;
// 这里输出 x 为 10
{
var x = 2;
// 这里输出 x 为 2
}
// 这里输出 x 为 2
let 关键字就可以解决这个问题,因为它只在 let 命令所在的代码块 {} 内有效。
var x = 10;
// 这里输出 x 为 10
{
let x = 2;
// 这里输出 x 为 2
}
// 这里输出 x 为 10
- 循环作用域
使用 var 关键字:
var i = 5;
for (var i = 0; i < 10; i++) {
// 一些代码...
}
// 这里输出 i 为 10
使用 let 关键字:
let i = 5;
for (let i = 0; i < 10; i++) {
// 一些代码...
}
// 这里输出 i 为 5
在第一个实例中,使用了 var 关键字,它声明的变量是全局的,包括循环体内与循环体外。
在第二个实例中,使用 let 关键字, 它声明的变量作用域只在循环体内,循环体外的变量不受影响。
- 相同点局部变量和全局变量
//它们的作用域都是 局部的:
// 使用 var
function myFunction() {
var carName = "Volvo"; // 局部作用域
}
// 使用 let
function myFunction() {
let carName = "Volvo"; // 局部作用域
}
//在函数体外或代码块外使用 var 和 let 关键字声明的变量也有点类似。
//它们的作用域都是 全局的:
// 使用 var
var x = 2; // 全局作用域
// 使用 let
let x = 2; // 全局作用域
- HTML 代码中使用全局变量
在 JavaScript 中, 全局作用域是针对 JavaScript 环境。
在 HTML 中, 全局作用域是针对 window 对象。
使用 var 关键字声明的全局作用域变量属于 window 对象:
var carName = "Volvo";
// 可以使用 window.carName 访问变量
使用 let 关键字声明的全局作用域变量不属于 window 对象:
let carName = "Volvo";
// 不能使用 window.carName 访问变量
- const关键字
const 用于声明一个或多个常量,声明时必须进行初始化,且初始化后值不可再修改:
const PI = 3.141592653589793;
PI = 3.14; // 报错
PI = PI + 10; // 报错
- const与let异同
const定义常量与使用let 定义的变量相似:
- 二者都是块级作用域
- 都不能和它所在作用域内的其他变量或函数拥有相同的名称
两者还有以下两点区别:
- const声明的常量必须初始化,而let声明的变量不用
- const 定义常量的值不能通过再赋值修改,也不能再次声明。而 let 定义的变量值可以修改。
var x = 10;
// 这里输出 x 为 10
{
const x = 2;
// 这里输出 x 为 2
}
// 这里输出 x 为 10
const 声明的常量必须初始化:
// 错误写法
const PI;
PI = 3.14159265359;
// 正确写法
const PI = 3.14159265359;
- 并非真正的常量
const 的本质: const 定义的变量并非常量,并非不可变,它定义了一个常量引用一个值。使用 const 定义的对象或者数组,其实是可变的。可以使用Object.freeze()方法来 冻结变量 下面的代码并不会报错:
// 创建常量对象
const car = {type:"Fiat", model:"500", color:"white"};
// 修改属性:
car.color = "red";
// 添加属性
car.owner = "Johnson";
但是我们不能对常量对象重新赋值:
const car = {type:"Fiat", model:"500", color:"white"};
car = {type:"Volvo", model:"EX60", color:"red"}; // 错误
以下实例修改常量数组:
// 创建常量数组
const cars = ["Saab", "Volvo", "BMW"];
// 修改元素
cars[0] = "Toyota";
// 添加元素
cars.push("Audi");
但是我们不能对常量数组重新赋值:
const cars = ["Saab", "Volvo", "BMW"];
cars = ["Toyota", "Volvo", "Audi"]; // 错误
- 重置变量
使用 var 关键字声明的变量在任何地方都可以修改:
var x = 2;
// x 为 2
var x = 3;
// 现在 x 为 3
在相同的作用域或块级作用域中,不能使用 let 关键字来重置 var 关键字声明的变量:
var x = 2; // 合法
let x = 3; // 不合法
{
var x = 4; // 合法
let x = 5 // 不合法
}
在相同的作用域或块级作用域中,不能使用 let 关键字来重置 let 关键字声明的变量:
let x = 2; // 合法
let x = 3; // 不合法
{
let x = 4; // 合法
let x = 5; // 不合法
}
在相同的作用域或块级作用域中,不能使用 var 关键字来重置 let 关键字声明的变量:
let x = 2; // 合法
var x = 3; // 不合法
{
let x = 4; // 合法
var x = 5; // 不合法
}
let 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的:
let x = 2; // 合法
{
let x = 3; // 合法
}
{
let x = 4; // 合法
}
在相同的作用域或块级作用域中,不能使用 const 关键字来重置 var 和 let关键字声明的变量:
var x = 2; // 合法
const x = 2; // 不合法
{
let x = 2; // 合法
const x = 2; // 不合法
}
在相同的作用域或块级作用域中,不能使用 const 关键字来重置 const 关键字声明的变量:
const x = 2; // 合法
const x = 3; // 不合法
x = 3; // 不合法
var x = 3; // 不合法
let x = 3; // 不合法
{
const x = 2; // 合法
const x = 3; // 不合法
x = 3; // 不合法
var x = 3; // 不合法
let x = 3; // 不合法
}
const 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的:
const x = 2; // 合法
{
const x = 3; // 合法
}
{
const x = 4; // 合法
}
- 变量提升
JavaScript var 关键字定义的变量可以在使用后声明,也就是变量可以先使用再声明
carName = "Volvo"; // 这里可以使用 carName 变量
var carName;
let 关键字定义的变量则不可以在使用后声明,也就是变量需要先声明再使用。
// 在这里不可以使用 carName 变量
let carName;
const 关键字定义的变量则不可以在使用后声明,也就是变量需要先声明再使用。
carName = "Volvo"; // 在这里不可以使用 carName 变量
const carName = "Volvo";
- 小结
- 使用var关键字声明的全局作用域变量属于window对象。
- 使用let关键字声明的全局作用域变量不属于window对象。
- 使用var关键字声明的变量在任何地方都可以修改。
- 在相同的作用域或块级作用域中,不能使用let关键字来重置var关键字声明的变量。
- 在相同的作用域或块级作用域中,不能使用let关键字来重置let关键字声明的变量。
- let关键字在不同作用域,或不用块级作用域中是可以重新声明赋值的。
- 在相同的作用域或块级作用域中,不能使用const关键字来重置var和let关键字声明的变量。
- 在相同的作用域或块级作用域中,不能使用const关键字来重置const关键字声明的变量
- const 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的:
- var关键字定义的变量可以先使用后声明。
- let关键字定义的变量需要先声明再使用。
- const关键字定义的常量,声明时必须进行初始化,且初始化后不可再修改。
this
- this
- 在方法中,this 表示该方法所属的对象。
- 如果单独使用,this 表示全局对象。
- 在函数中,this 表示全局对象。
- 在函数中,在严格模式下,this 是未定义的(undefined)。
- 在事件中,this 表示接收事件的元素。
- 箭头函数的this指向指向箭头函数定义时所处的对象,而不是箭头函数使用时所在的对象,默认使用父级的this
- 类似 call() 和 apply() 方法可以将 this 引用到任何对象。
在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。
在下面实例中,当我们使用 person2 作为参数来调用 person1.fullName 方法时, this 将指向 person2, 即便它是 person1 的方法
var person1 = {
fullName: function() {
return this.firstName + " " + this.lastName;
}
}
var person2 = {
firstName:"John",
lastName: "Doe",
}
person1.fullName.call(person2); // 返回 "John Doe"
- this的多种指向
- 1在对象方法中, this 指向调用它所在方法的对象。
- 单独使用 this,它指向全局(Global)对象。
- 函数使用中,this 指向函数的所属者。
- 严格模式下函数是没有绑定到 this 上,这时候 this 是 undefined。
- 在 HTML 事件句柄中,this 指向了接收事件的 HTML 元素。
- apply 和 call 允许切换函数执行的上下文环境(context),即 this 绑定的对象,可以将 this 引用到任何对象。
闭包
- 为什么要闭包
全局变量的作用域是全局性的,即在整个JavaScript程序中,全局变量处处都在。
而在函数内部声明的变量,只在函数内部起作用。这些变量是局部变量,作用域是局部性的;函数的参数也是局部性的,只在函数内部起作用。
- 导入计时器问题
- 如果使用全局变量
var counter = 0;
function add() {
return counter += 1;
}
add();
add();
add();
// 计数器现在为 3
计数器数值在执行 add() 函数时发生变化。
但问题来了,页面上的任何脚本都能改变计数器,即便没有调用 add() 函数。(counter会受到污染)
如果使用局部变量
function add() {
var counter = 0;
return counter += 1;
}
add();
add();
add();
// 本意是想输出 3, 但事与愿违,输出的都是 1 !
以上代码将无法正确输出,每次我调用 add() 函数,计数器都会设置为 1。
- 解决思路------JavaScript 内嵌函数
所有函数都能访问全局变量。
实际上,在 JavaScript 中,所有函数都能访问它们上一层的作用域。
JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量。
该实例中,内嵌函数 plus() 可以访问父函数的 counter 变量:
function add() {
var counter = 0;
function plus() {counter += 1;}
plus();
return counter;
}
弊端:如果我们能在外部访问 plus() 函数,这样就能解决计数器的困境。
我们同样需要确保 counter = 0 只执行一次。
我们需要闭包。
- 解决方法------闭包
导读:
内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量。
外层作用域下无法获取内层作用域下的变量。
特性:
- 函数嵌套函数
- 函数内部可以引用函数外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
解析:
function a (){
var name = "dov";
return function(){
return name;
}
}
var b =a();
在这段代码中,a()中的返回值是一个匿名函数,这个函数在a()作用域内部,所以它可以获取a()作用域下变量name的值,将这个值作为返回值赋给全局作用域下的变量b,实现了在全局变量下获取到局部变量中的变量的值
function fn(){
var num =3;
return function (){
var n=0;
console.log(++n);
console.log(++num);
}
}
var fn1=fn();
fn1()//1 4
fn1()//1 5
一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁,但是在这个例子中,匿名函数作为fn的返回值被赋值给了fn1,这时候相当于fn1=function(){var n = 0 ... },并且匿名函数内部引用着fn里的变量num,所以变量num无法被销毁,而变量n是每次被调用时新创建的,所以每次fn1执行完后它就把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的num,于是这里就产生了内存消耗的问题
- 闭包的好处与坏处
好处:
- 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
- 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
- 匿名自执行函数可以减少内存消耗
坏处:
- 其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
- 其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
事件冒泡和事件捕获
- 事件的传播
微软公司:认为事件应该是由内向外传播,也就是当事件触发时,应该先触发当前元素上的事件,然后再向当前元素的祖先元素上传播,也就说事件应该在冒泡阶段执行
网景公司:认为事件应该是由外向内传播的,也就是当前事件触发时,应该先触发当前元素的最外层的祖先元素的事件,然后在向传播给后代元素
w3c将事件传播分成了三个阶段
捕获阶段:
在捕获阶段时从最外层的祖先元素,向目标元素进行事件的捕获,但是默认此时不会触发事件
目标阶段
事件捕获到目标元素,捕获结束开始在目标元素上触发事件
冒泡阶段
事件从目标元素向他的祖先元素传递,依次触发祖先元素上的事件
如果希望在捕获阶段就触发事件,可以将addEventListener()的第三个参数设置为true,一般情况下我们不会希望在捕获阶段触发事件,所以这个参数一般都是false
(ie8以下没有捕获阶段)
- 停止冒泡
通常,冒泡向上上升到<html>,然后上升到Document象。某些事件甚至可以到达window对象和调用处理程序。当事件已被完全处理时,处理程序都可以决定是否停止冒泡
- event.stopPropagation();
(这是阻止事件的冒泡方法,不让事件向documen上蔓延,但是默认事件任然会执行)
- event.preventDefault();
(阻止默认事件的方法,在许多事件的监听回调中调用preventDefault()前,都需要检查 cancelable 属性的值。 event.cancelable 属性来判断一个事件的默认动作是否可以被取消.. 在cancelable属性为false的事件上调用 preventDefault 方法没有任何效果.)
(什么是默认事件,例如浏览器默认右键菜单、a标签默认连接跳转,表单提交...)
- return false;
(可以理解为return false就等于同时调event.stopPropagation()event.preventDefault())
回调函数
- 什么是回调函数
setTimeout(function(){},20)//计时器就是一个回调函数
字面上的理解,回调函数就是传递一个参数化的函数,就是将这个函数作为一个参数传到另一个主函数里面,当那一个主函数执行完之后,再执行传进去的作为参数的函数。走这个过程的参数化的函数 就叫做回调函数。换个说法也就是被作为参数传递到另一个函数(主函数)的那个函数就叫做 回调函数。
常用事件
- 鼠标事件
- 键盘事件
- keydown:当用户按下键盘上任意键时触发,如果按住不放,会重复触发;
- keyup:当用户释放键盘上的键触发;
- keypress:当用户按下键盘上的字符键时触发,如果按住不放,会重复触发;
- HTML事件
scroll()
//此方法接收两个参数,依次为X坐标和Y坐标;设置滚动条的偏移位置
scrollTo()
//此方法和scroll()作用一样,都是设置滚动条的偏移位置。
scrollBy()
//此法发同样接收两个参数,不过参数分别为X轴的偏移量和Y轴的偏移量,并且可以增加或者减少。
scroll(0, 200);
//设置滚动条Y轴位置在200像素的地方。比如:当前坐标为0,执行后便是200,当前坐标为100,执行后是200。
scrollTo(0, 200);
//同scroll()方法,设置Y轴在200像素的位置。
scrollBy(0, 200);
//使得滚动条Y轴的位置,在当前的基础上增加200。比如:当前Y轴位置为0,执行后便是200;当前为100,执行后便是300。
window.scrollTo(x-coord,y-coord)
//x-coord是文档中的横轴坐标
//y-coord是文档中的纵轴坐标
window.scrollTo(0,1000);
//垂直滚动到1000的位置
window.scrollTo(options)
top等同于 y-coord
left等同于x-coord
behavior 类型String,表示滚动行为,支持参数smooth(平滑滚动),instant(瞬间滚动),默认值未auto,等同于instant
例:window.scrollTo({
top:1000,
behavior:"smooth";
})
事件的绑定
- 在DOM元素中直接绑定
<div οnclick="fun()"></div>
缺点:
- 存在一个时差问题,因为用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件(比如js代码还没有下载下来),由此会引发错误。
- HTML与js代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML代码和JS代码,这非常不利于后期代码的维护。所以这种方法在开发中基本不用,但是还是得了解它是怎么个用法和为什么不用它的原因。
- 在JavaScript代码中绑定
var oBox = document.getElementById("container");
oBox.onclick = function() {
.....
}
缺点:只能同时为一个元素的一个事件绑定一个响应函数,不能绑定多个,如果绑定多个,则后面的会覆盖前面的
- 绑定事件监听函数
addEventListener()添加事件
removeEventListener()删除事件
执行顺序与添加顺序相同,两个方法都接受三个参数:
第一个参数:事件名称(不加on)
第一个参数:作为事件处理程序的函数
第一个参数:捕获值false(不捕获)/true(捕获),不写表示默认值false
var oBox = document.getElementById("container");
oBox.addEventListener("click",fn(),false);
oBox.removeEventListener("click",fn(),false);
function fn(){//执行代码}
attachEvent()添加事件
detachEvent()删除事件
执行顺序与添加顺序相反,由于(IE8-)不支持捕获,所以两种方法只支持两个参数:
第一个参数:事件名称(加on)
第二个参数:作为事件处理程序的函数(回调函数)
var oBox = document.getElementById("container");
oBox.attach("click",fn());
oBox.detach("click",fn());
function fn(){//执行函数}
定时器
- Timimg事件
JavaScript可以在时间间隔内执行
这就是所谓的定时事件
- setTimeout()方法---------延时(只执行一次)
window.setTimeout(function,milliseconds);
window.setTimeout()方法可以不带window前缀来编写
第一个参数是要执行的函数
第二个参数指示执行之前的毫秒数
<button onclick="setTimeout(myFunction,3000)">试一试</button>
<script>
function myFunction(){
alert("Hello");
}
</script>
- 如何停止setTimeout()
clearTimeout()方法停止执行setTimeout()中规定的函数‘
window.clearTimeout(timeoutVariable);
window.clearTimeout()方法可以不带window前缀来写
clearTimeout()使用setTimeout()返回的变量
myVar =setTimeout(function,milliseconds);
clearTimeout(myVar);
- setlnterval()方法---------定时(会循环执行)
window.setInterval(function,milliseconds);
window.setTimeout()方法可以不带window前缀来编写
第一个参数是要执行的函数
第二个参数每个执行之间的时间间隔的长度
var myVar =setInterval(myTimer,1000);
function myTimer(){
var d =new Date();
document.gerElementById("demo").innerHTML=d.toLocaleTimeString();
}
- 如何停止setlnterval()方法
clearInterval()方法停止setInterval()方法中指定的函数的执行
window.clearInterval(timerVariable);
window.clearInterval()方法可以不带window前缀来写
clearInterval()方法使用从setInterval()返回的变量
myVar =setInterval(function,milliseconds);
clearInterval(myVar);
- 延时器做出定时器的效果
当然有的时候我们想用延时器做出定时器的效果,让它一直执行也可以,就是反复调用函数自身即可
fun();
function fun() {
console.log(1)
setTimeout("fun()",1000); //自身调用,重复执行
}
- 定时器叠加问题
当我们多次点击浏览器窗口时会发现,打印的速度越来越快,这就是我们所说的定时器累加。
var timer;
document.onclick = function() {
timer = setInterval(function(){
console.log(1)
},1000)
}
那么为什么会出现这样的情况呢?咱们举个例子,这就好像一个人每隔一秒钟使用一次打印机,点两下的话就相当于两个人去使用这个打印机,他们是同时进行的,因此每一秒打印会有多于原来两倍的速度。那么如何解决这个问题呢?
定时器累加问题的解决:先清除定时器,再使用定时器。代码示例如下:
var timer;
document.onclick = function() {
clearInterval(timer)
timer = setInterval(function(){
console.log(1)
},1000)
}
- 节流阀
对于节流阀的理解,举个不恰当的例子,使用节流阀就像在一个宾馆睡觉,一间屋子只能睡一个人。
第一步,进入房间,默认门是开着的(flag=true),所以可以直接进来睡觉;
第二步,为了确保安全,需要将门关上(flag=false);
第三步,睡觉结束(一个事件完成),需要离开让下一个进来睡觉,将门打开(flag=true)。
这样如此反复,确保在睡觉这件事情上,一次只能睡一个人。
<button>按钮</button>
<script>
var btn = document.querySelector('button');
var flag = true;
var timer;
btn.onclick = function () {
clearTimeout(timer)//延时器可以写在if里面,也可以写在外面,如果写在外面,必须清理延时器,否则会创建多个延时器,导致flag很快为true,就一直输出1;如果写在里面,想要执行定时器,必须通过if语句,确保了定时器的安全
if (flag == true) {
flag = false;
console.log(1);
}
timer = setTimeout(function () {
flag = true;
}, 2000)
}
</script>
- 定时器重置:
略
- 排他思想
案例:表格换行变色
- 箭头函数
//ES5
var x =function(x,y){
return x*y;
}
//ES6
const x =(x,y)=> x*y;
特点:
const x=(x,y)=>{return x*y};
- 严格模式
通过在脚本或函数的开头添加"use strict";来声明严格模式
在脚本开头进行声明,拥有全局作用域(脚本中的所有代码均以严格模式来执行)
在函数中声明严格模式,拥有局部作用域(只有函数中的代码以严格模式)
- 为什么使用严格模式
严格模式使我们更容易编写安全的JavaScript
严格模式把之前可接受的“坏语法”转变未真实的错误
- 严格模式中不允许的事项
- 在不声明对象的情况下使用对象也是不允许的
删除变量(或对象)是不允许
重复参数名是不允许的
8进制数值文本是不允许的
转义字符是不允许的
写入只读属性是不允许的
写入只能获取的属性是不允许的
删除不可删除的属性是不允许的
字符串”eval“不可用作变量
字符串”arguments“不可用作变量
with语句是不允许的