-----2019年5月25日
目录
1遍历对象的属性
for-in语句可以用于遍历对象的属性名及属性值。
for(var attribute_name in obj){
console.log(obj[attribute_name]);
}
访问对象有两种方式“.”和[],因为这里用到了变量,所以只能使用obj[attribute_name]的方法实现
2js中的作用域
2.1变量作用域
- 全局作用域。全局作用域在页面打开时创建,页面关闭时销毁,全局作用域中创建的变量都会作为window的对象属性保存。因此直接 为一个变量赋值而不声明,会认为是为window添加的属性。
注:声明但不赋值与未声明的变量都是undefined的,而使用访问未声明的变量会报错,但是访问声明的变量只会按undefined类型处理。
- 声明提前
var a =5;
使用var关键字声明的变量,会在所有代码执行之前被声明,赋值是在改代码原来所在位置执行。
除此之外,函数创建中的声明形式也会被声明提前,也即如下代码是可以执行的,因为fun的函数被声明提前。
fun();
function fun(){
//函数体
}
2.2函数作用域
- 每调用一次,函数就会创建一个新的函数作用域,他们之间的访问时互相独立的。
- 函数作用域可以访问到全局作用域的变量(全局变量),全局作用域无法访问到函数作用域的变量。
- 当在函数作用域中操作一个变量,它会先在自身变量作用域寻找,如果有就使用,如果没有向上一级寻找,上一级没有再向上一级访问,以此类推,直至在全局作用域没有访问到,就报错。
- 如果想跳过上述的变量搜寻准则,可以直接使用window.变量名访问全局作用域中的方法和变量
- 在函数作用域也有变量提前,是在函数代码执行之初就已经变量声明。
- 在函数内部,不使用var声明直接赋值的变量,会被当成是window的属性添加进去。
- 形参,相当于在函数作用域中声明。
2.3函数闭包
3DEBUG
- 选中要观察的变量,右键 添加监控(add selected text to watches)
- 但步执行快捷键:F10
4this的情况
解析器就是浏览器,解析器在调用函数每次都会向函数内部穿传递一个隐含参数,这个隐含的参数是this。this指向一个对象,这对象我们称之为上下文对象,根据函数的调用方式不同,this指向不同的对象
(1)以函数的形式调用。this指的永远是window,不管是在函数内部再创建函数,还是将函数返回。
function fun(){
alert(this);
function fun1(){
alert(this);
}
return fun1;
}
fun()();
运行结果
(2)以方法的形式调用。this指的是调用方法的对象。
(3)以构造函数的形式调用。this指的是新创建的那个对象
(4)使用call和apply调用时,this为指定的那个对象。
(5)事件的响应函数中,响应函数给谁绑定,this就指的谁。
5对象的创建
5.1工厂模式
封装为一个函数,传进来对象的属性值,方法体。
function CreateObj (name, age, sex) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.sayName = function () {
alert(this.name);
}
return obj;
}
缺点:无法区分一个对象的类型,因为这种方式创建出来的对象都是Object类型的。
5.2构造函数模式
构造函数与普通的函数类似,为了区别于普通函数,将构造函数的首字母大写。
构造函数的创建流程
- 立即创建一个新对象
- 将新建对象设置为函数中的this
- 执行构造函数中的代码
- 将新建对象作为返回值返回
类的概念
由于引入了构造函数,我们称使用同一个构造函数创建的对象,我们称之为一类对象,也将一个构造函数成为一个类。可以通过 instanceof 判断一个对象是否是一个类的实例。
语法:如果对象是构造函数的一个实例,返回true,否则返回false
对象名 instanceof 构造函数
缺陷:每个新的对象都会在堆区开辟一片内存空间放置变量的属性和方法,每个对象的地址是不同的,所以不同的对象也是独立。事实上有一些属性和方法是可以共用的,这些重复的属性和方法浪费了内存空间。
一种解决办法是在全局作用域创建相关的属性和函数,然后为对象赋相同的函数对象地址,从而保证所有一个类下的对象共用一个方法。但是这种方法会污染全局作用域的命名空间,而且很不安全。
5.3原型创建对象
每创建一个函数,解析器会向函数对象添加一个属性 prototype(原型),这个属性对应于一个对象,称为原型对象。
用图:原型对象保存着 所有通过同一构造函数创建的实例的共有属性和方法。通过原型对象可以实现方法和属性的共用。
创建方法:
- 使用构造函数。Person.prototype.sayName = function(){}
- 使用已经实例化的对象。per1.__proto__.sayName = function(){}
使用方法:
- 当我们访问一个对象的属性或者方法时,会先在对象自身中寻找,如果有就使用,没有再去原型中寻找。
- 使用in可以检查对象中是否含有某个属性,但是如果对象自身中没有,而在原型对象中含有使用in也会返回true。如果想检测自身是够含有该属性,可使用对象的hasOwnProperty()方法检查自身是够含有该属性。
- 原型对象也是对象,也含有隐含的属性:原型对象
- 例如想修改对象原有的toString()方法,可以类额原型中编写自定义的toString方法。
6数组(Array)
- 创建数组
字面量创建:var arr = [1,2,3,4,5];
使用构造函数创建:多个参数下,var arr = new Array(1,2,3,4,5);与字面量创建的效果一样,一个参数,var arr = new Array(5),表示创建一个长度为5的数组。
- 设置数组元素
arr[索引值]=元素值
数组中的数据类型可以是任何类型
- 获取数组的长度
对于连续的数组,使用数组的length属性会返回数组长度(元素的个数),而非连续的数组,使用length返回数组最大索引值+1(故尽量不要使用非连续的数组),即空的元素也算一个位置。
- 修改数组的长度
大于原长度,会空出后边的元素位置,但是这些空的也是占位置的。小于原长度,多出来元素会被删除。
注:修改length后,再push元素,是从索引值为lenth的位置添加的,其前的空元素也是算一个元素。
- 数组中常用的方法
(1)push,在数组的末尾添加一个或者多个元素,并返回新数组的长度
(2)pop,删除数组最后一个元素,并返回该元素。
(3)unshift,向数组的开头添加一个或者多个元素。并返回新数组的长度。
(4)shift,删除数组开头第一个元素,并返回删除的元素
- 数组中的迭代方法
这些方法适用于IE9+以上的浏览器,而且需要一个回调函数作为参数。
回调函数接受三个参数:element_value,element_index、array。数组中有几个参数就会执行这个回调函数几次。
(1)every。如对每一项返回ture,则返回true
(2)some。如果对任意一项返回true,则返回true
(3)forEach。对数组中每一项运行给定程序,无返回值
(4)map。对数组中的每一项运行给定的程序,有返回值
(5)filter。返回以返回true的项组成的数组。
- 数组中的slice和splic方法
(1)slice方法。从数组中提取指定的元素,不会影响到原来的数组。
var arr_slice = arr.slice(参数1,参数2);
参数1表示开始的元素索引,参数2表示截取的结束项的索引值(前闭后开)。
注:参数2缺省,就会一直截取到最后一个元素;
注索引值可以是负值,表示从后往前数,但是参数1一定要小于参数2,否则取得的元素是空。
(2)splice方法。从数组中删除指定元素,使用splice会影响到原数组。
arr.splice(参数1,参数2,参数3,参数4.......);
参数1表示开始位置的索引,参数2表示删除的数量,参数3及以后的参数表示要插到开始索引前边。因此splice有很多功能:
-
删除元素。参数3及以后参数缺省
-
替换元素。参数2不为空,有参数3以后的参数
-
插入元素。参数2为空“”(注意不是缺省)
- 练习:数组去重?
function duplicates(arr) {
var arr_prc = [];
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i] == arr[j]){
//如果之前push过,应该不再push了,否则arr_prc中会出现重复
var flag = true;
for(var k=0; k<arr_prc.length; k++){
if(arr[i]==arr_prc[k]){
flag = false;
}
}
if(flag){
//当有重复就把该检查出的重复元素删掉,以免下次再次碰见
arr_prc.push(arr[i]);
arr.splice(i,1);
break;
}
}
}
}
return arr_prc;
}
var a = [1, 2, 4, 4, 3, 3, 1, 5, 3];
var a_p = duplicates(a);
console.log(a_p);
- 数组中的其他方法
(1)contact。连接两个或者多个数组
(2)join。数组转化为字符串
(3)reverse。可用于翻转数组。会影响原本数组
(4)sort。排序(默认按Unicode编码进行排序,即使是对纯数字的数组,也是这样)。会影响原本数组
如果想自定义排序的规则,可以想sort方法中传入回调函数作为参数,回调函数需要两个形参,第一个参数对应的数组元素索引一定小于第二个参数的索引。
所以,如果回调函数返回一个大于0的值,形参对应的元素在数组中交换位置;如果返回小于等于0的书,顺序都不变。
7函数
- call和apply方法。这两个都是函数对象的方法,需要通过函数对象调用。
功能:当对函数调用call()和apply()时,都会执行函数内的代码。
call()函数用法:函数对象名.call(参数1,参数2,参数2......), 参数1将会成为函数执行时的this,参数2及以后的参数会作为实参传给函数。
apply()函数的用法:函数对象名.apply(参数1,参数2),参数1与call函数相同,会成为函数执行时的this,参数2是包含参数的数组,即将要传入参数的实参全部封装于一个数组当中。
用处:call和apply方法真正强大的地方是能够扩充函数赖以运行的作用域。
注意:单纯说函数应该是为window添加的方法,但是对象与函数不需要任何的耦合关系。如下的代码
myObj.sayName.call(obj);
sayName中alert的this.name指的是obj的name,而非myObj的name。注意结合第4章中的this的情况记忆。
- arguments属性
调用函数时,浏览器都会穿给函数两个隐含参数:
- this的上下文对象
- 包含实参的对象arguments
注意:arguments可以用数组索引的方式访问,但是只是类数组对象,不是数组,arguments instanceOf Array返回false,Array.isArray(arguments)也返回的是false。arguments中有一个属性callee,表示当前正在执行的函数对象
8Date对象
- 创建Date对象
var d = new Date();
直接用构造函数(缺省参数)创建的Date对象,指的当前代码执行的时间。如果在构造函数中传入“月份/日/年 时:分:秒”的字符串,d指的就是这个时间。
- 使用Date对象
d.getDate(); 获取几号
d.getDay(); 获取周几
d.getMonth(); 获取几月份(返回0-11月份)
d.getFullYear(); 获取年份
类似的还有getHours,getMinutes,getSeconds
d.detTime(); 获取当前日期的时间戳(毫秒数)。
注:时间戳是只从GMT 1970年1月1日,0时0分0秒到当前日期所花的毫秒数。计算机底层在保存时间时使用的都是时间戳。
time = Date.new(); 获取当前代码执行时的时间戳
9window和Global对象
web浏览器将Global对象作为window的一部分加以实现,isNaN、isFinite、parseInt以及parseFloat实际上都是global对象的方法。
10包装类
- String、Number、Boolean
功能:将基本的数据类型转化为对象,这样可以使用对象的方式(方法和属性)对基本数据类型进行处理。因为方法和属性只能添加给对象,不能添加给基本数据类型。
但是,开发中一般不用包装类,因为会产生一些不可预料的后果。
引入包装类的原因:当我们对一些基本数据类型去访问属性、方法时,浏览器会临时使用包装类将基本数据类型转化为对象,然后调用对象的属性和方法,调用完再转化为基本数据类型。这也就是所谓的装箱拆箱。包装类可以帮助我们理解这一特性。
所以,我们可以通过基本数据的包装类的原型去设置一些方法和属性,方便装箱拆箱的时候使用。
- String是最常用的包装类
字符串在底层是以字符数组的形式保存的。以下方法不会对源字符串产生影响,产生变化的字符串都会作为返回值返回,replace方法也不会对源字符串缠身影响。
(1)charAt。返回字符中指定位置的字符,根据索引获取字符。
(2)contact。连接两个或者多个字符串。
(3)indexOf(参数1,参数2)。检索一个字符串是否有指定的内容,从前向后找,如果有返回第一次出现的位置,如果没有返回-1,第二参数表示从哪个字符开始想后寻找。
(4)lastIndexOf(参数1,参数2)。检索一个字符串是否有指定的内容,从后向前找,如果有返回第一次出现的位置,如果没有返回-1,第二参数表示从哪个字符开始向前找。
(5)slice()。与数组的slice方法类似。
(6)subString。截取一个字符串,与slice相似,不同的是这个方法不接受负值作为参数,如果传如一个负值,则默认0,而且会自动调整参数的位置,让第一个参数小于第二个参数。
(7)split。可以将字符串拆分为一个数组。接受的参数作为拆分的标准。如果传入空串,则将每个字符转为数组元素。可以传进来一个正则表达式对象作为参数。
(8)search。搜索字符串中是否含有指定内容的索引,如果有返回第一次出现的索引,否则返回-1,可传入正则对象。
(9)replace。将查找到的内容替换为新的内容。
用法:str.replace(参数1,参数2);
参数1可以接受一个正则表达式作为参数,参数2是新的内容。默认只会替换第一个满足条件的字符串。需要删除只需要让参数2为空串“”。
注意:split未指定“g”模式,也会全局拆分;search指定“g”模式,也只会搜索第一次出现的;match和replace默认只会匹配第一个,加“g”才会匹配全局的字符串。search和indexOf的区别,search可传入正则表达式作为参数,而indexOf只能传入字符串。
11DOM操作
DOM是API,是JavaScript为了操作HTML文档所提供的接口。document指的就是整个文档。
(1)节点
每个节点都是一个对象,共有四种节点:文档节点、元素节点、属性节点、文本节点。
(2)获取节点对象的方法
- 通过document。有:getElementById,通过标签的ID属性获得元素节点对象;getElementsByTagName通过标签名获取一组节点对象,封装到一个数组当中,即使只有一个节点对象,也是用长度为1的数组包装;getElementsByName,数组的特性特性与getElementsByTagName类似,但是这种方式主要用于操作表单项。
- 获取节点的子节点对象。有:getElementsByTagName,返回当前节点的指定标签名后代的元素节点;childNodes属性,表示当前节点的所有子节点;firstChild属性,子节点中第一个子节点;lastChild属性,最后一个子节点。
- children属性,获取当前元素的所有子元素。
- parentNode。获取元素父元素
- previousSibling。返回当前元素的前一个兄弟节点。
- nextSibling。获取当前节点的后一个兄弟节点
- 获取body节点对象。var body = document.getElementsByTagName("body")[0]; 另外,document中有一个属性body保存的是body的引用。
- html = document.documentElement;
- all = document.all;
- 根据CSS选择器查询一个元素节点对象。document.querySelector(".box1 div");IE8中虽然没有getElementsByClassName,但是可以使用querySelector代替,但是它只能返回唯一的元素对象(第一个满足条件的元素对象),即使满足条件的元素节点对象有多个。
- querySelectorAll。该方法与querySelector类似,不同的是它将符合条件的元素封装到一个数组中,解释只有一个元素对象符合条件,也会封装到长度为1的数组中。
注:上边标红的节点,可能包含文本节点对象,所以如果仅仅选中元素节点的话,应避免使用。
(3)DOM增删改
- 创建元素节点对象——createElement()
语法:document.createElement("....");
用法:它需要一个标签名作为参数,并根据标签名创建元素节点对象,并将创建好的节点对象返回。
- 创建文本节点对象——createTextNode()
语法:document.createTextNode(".....");
用法:需要文本内容作为参数,并根据该内容创建文本节点,并将节点返回。
- 添加新节点——appendChild();
语法: fatherNode.apendChild(childNode);
- 在兄弟元素前插入节点
在指定的子节点前插入新的子节点------insertBefore()
语法: fatherNode.insertBefore(newNode, oldNode);
- 替换子节点
father_node.replaceChild(new_node,old_node);
- 删除子节点
father_node.removeChild(child_node);
注意:使用innerHTML也可以完成DOM的增删改的相关操作,
var city = document.getElementById("city");
city.innerHTML +="<li>广州</li>"
但我们一般情况下,我们会两中方式结合使用,推荐使用
Var city = document.getElementById("city");
var li = document.createElement("li");
li.innerHTML = "广州";
city.appendChild(li);
练习
表格+表单实现 对表格的增删操作
思路:
(1)为每个超链接都绑定一个响应事件
问题:点击超链接时,页面会跳转?
这是超连接的默认行为,此时我们不希望默认行为,可以通过响应函数的最后return false来消除默认行为.
(2)响应事件中,建立超链接和该表格一行之间的关系,从而能删除这一行.
问题1:如何获取表格的一行?
根据this的情况,事件的this是 给谁绑定,this就指的是谁. 因此可以通过this.parentNode.parentNode获取一行的元素对象tr.
问题2:为防止误操作,在删除之前应该有一个提示的文本?
类似alert的方法,应该都是window对象的方法,其中alert是警告框,confirm是确认框,prompt是提示框.其中confirm(str),用于弹出一个 str为内容的提示框,需要一个字符串为参数,该字符串会作为提示文字显示出来,而且函数会返回 如果用户点击"确定",返回true,如果用户点击"取消",返回false.
问题3:提示框应该包含用户的名字,应该通过DOM操作获取含有名字的节点?
由问题1可获取到父级的父级节点tr,如果再次使用getElementsByTagName即可获取到所有子元素.
var name = tr.getElementsByTagName("td")[0].innerHTML;
(3)添加员工.点击按钮以后,将员工的信息添加到表格中(这句话将整个业务流程给描述了,所以以下的代码都是将这句话给具体化)
注意:写代码的时候,实现什么样的功能,先在心里边想一下功能了的业务流程到底是什么样的,因为写代码就是将自己的思路用代码实现.
问题1:获取文本框中 员工的名字
首先获取文本框的对象,然后借助文本框的value的属性获取文本框内的内容
var name = document.getElementById("nameId").value;
问题2:创建四个td,一个tr对象,然后
问题3:超链接是黑色的,不能点击
向a中添加href属性
a.href = "#";
问题4:新添加的行,不能实现删除操作?
这是因为没有为新添加的超链接添加事件.为了简化代码,可以封装事件函数,因为每个删除操作时几乎完全一样的.
(4)简化代码
采用innerHTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>增删DOM练习</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
#tab {
border-collapse: collapse;
margin: 10px auto;
}
#tab th,td {
height: 20px;
line-height: 20px;
padding: 5px;
text-align: center;
}
#fm {
padding: 5px;
border: 2px solid #ccc;
border-radius: 3px;
margin: 20px auto;
width: 250px;
overflow: hidden;
}
#sub_btn {
display: block;
width: 3em;
margin: 5px auto;
}
</style>
<script type="text/javascript">
window.onload = function () {
// 首选实现删除的功能
// 为超链接节点对象绑定事件
var tab = document.getElementById("tab");
var a1 = tab.getElementsByTagName("a");
// 计算表格每行有的元素格式
var cols = tab.getElementsByTagName("tr")[1].getElementsByTagName("td").length;
// alert(cols);
var sub_btn = document.getElementById("sub_btn");
// 需要 访问与事件绑定对象关系不大的对象属性时,不可以直接使用ID作为参数传进,否则会出现问题,但是可以通过将事件函数放在一个匿名函数里边再将匿名函数绑定在onclick上,即可实现传实参的效果
// 为每个元素添加事件
for (var i = 0; i < a1.length; i++) {
a1[i].onclick = addEvent;
}
sub_btn.onclick = function(){
addNode("name", "age", "salary");
};
// 将div#fm内的信息以节点的形式添加到表格内的元素
// 创建tr节点
// var td_arr = [];
// for (var i = 0; i < cols-1; i++) {
// }
// 创建tr下的cols个td节点
// var fm = document.getElementById("fm");
// name, age, salary
function addNode (name, age, salary) {
var name_node = document.createElement("td");
var age_node = document.createElement("td");
var salary_node = document.createElement("td");
var alink_node = document.createElement("td");
var new_tr = document.createElement("tr");
var tbody = tab.getElementsByTagName("tbody")[0];
name_node.innerHTML = document.getElementById(name).value;
age_node.innerHTML = document.getElementById(age).value;
salary_node.innerHTML = document.getElementById(salary).value;
alink_node.innerHTML = "<td><a href='#'>删除</a></td>";
// 为alink_node添加事件
alink_node.children[0].onclick = addEvent;
//为新的new_tr添加td节点
new_tr.appendChild(name_node);
new_tr.appendChild(age_node);
new_tr.appendChild(salary_node);
new_tr.appendChild(alink_node);
// alert(tbody.innerHTML);
tbody.appendChild(new_tr);
}
function addEvent (){
// 删除超链接对应的行元素对象
var tr_node = this.parentNode.parentNode;
tr_node.parentNode.removeChild(tr_node);
return false;
}
// obj.onclick = function () {
// /* body... */
// }
}
</script>
</head>
<body>
<table id="tab" border="2">
<tr>
<th>姓名</th>
<th>年龄</th>
<th>薪水</th>
<th>操作</th>
</tr>
<tr id="cols1">
<td>Tom</td>
<td>12</td>
<td>3000</td>
<td><a href="#">删除</a></td>
</tr>
<tr id="cols2">
<td>Jim</td>
<td>18</td>
<td>4000</td>
<td><a href="#">删除</a></td>
</tr>
</table>
<div id="fm">
姓名:<input type="text" id="name">
<br/>
年龄:<input type="text" id="age">
<br/>
薪资:<input type="text" id="salary">
<br/>
<input type="submit" value="提交" id="sub_btn">
</div>
</body>
</html>
最终的效果: