FE知识点(硕哥)
目录
前传:
1.typeof和类型转换
正文:
1.作用域、作用域链([[scope]])
2.立即执行函数
3.闭包
4.对象、包装类
5.原型原型链
6.call、apply
7.继承模式、命名空间、对象枚举(对象遍历)
8.this笔试题讲解
9.arguments、克隆(深度克隆)
10.三目运算
11.数组、类数组
12.数组去重(三种基础数组去重)
13.try{}catch(e){}、es5标准模式
14.dom初探
15.dom选择器,节点类型
16.dom继承树,dom基本操作
17.date对象,定时器
18.获取窗口属性,获取dom尺寸,脚本化css
19.事件
20.json,异步加载,时间线
21.正则表达式
css使用手册
http://css.doyoe.com/
=================================
常规运算优先级相同的情况下是从前往后算,赋值运算是从后往前算
举例:
2 > 1 < 2 //true
var a = {n:1}
var b = a
a.n = a = {m:2} //这里会优先计算后面的
// a = {m:2} b = {n:{m:2}}
=================================
Math对象
属性:
1.Math.PI //返回圆周率
2.Math.SQRT2 //返回2的平方根
方法:
1.Math.abs(x) //返回x的绝对值
2.Math.ceil(x) //对x进行向上取整
3.Math.floor(x) //对x进行向下取整
4.Math.max(x,y) //返回x和y中的最大值
5.Math.min(x,y) //返回x和y中的最小值
6.Math.pow(x,y) //返回x的y次幂
7.Math.random() //返回0~1之间的随机数
8.Math.round(x) //四舍五入x
JavaScript引擎有精度不准的问题,例:0.14*100;
==================================
indexOf()方法用法是将indexOf前的字符串或数组之类的作为检索单位,括号中的字符或变量作为检索条件遍历数组,如果检索到该条件那么返回该条件首次出现的索引,如果全都遍历完并没有查询到该条件那么返回-1。
indexOf官方文档:
indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
stringObject.indexOf(searchvalue,fromindex)
第一个参数必填,规定需检索的字符串值。
第二个参数是可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。
=================================
前传:
1.typeof和类型转换
js中的原始数据类型:undefined、null、string、number、boolean、symbol(es6)
js中的引用类型:Object、Array、Function、RegExp、Date
包装类引用类型:String、Number、Boolean
** null和undefined之间的对比
null == undefined //true
null === undefined //false
typeof()方法访问任何值都不会报错,typeof()会把值类型返回,返回的结果是字符串。typeof()返回的值类型有string/number/boolean/object/function/undefined。注意:array和null都会被返回object,因为array就是特殊的对象,null是一个空引用对象。
typeof(a) //"undefined"
typeof(typeof(a)) //String
显示类型转换
Number(mix) //将目标类型转换成数字
parseInt(string, radix) //将目标转换成整型数字,如果加入第二个参数那么就是以第二个参数为基底将第一个参数转换为十进制,如果转换失败那么返回NaN。举例:parseInt(b,16) --> 11
parseFloat(string) // 将目标转换成浮点类型的数字
String(mix) //将目标类型转换成字符串
Boolean() //将目标类型转换成布尔值,只有 undefined、null、""、NaN、false、0这六个值为假,其它转换成布尔都为真。
target.toString(radix)
//undefined和null不能用toString,其它的都有toString方法。
//Number的toString:以target为值,十进制为基底转换成radix进制,举例:20.toString(8) --> 24
//Object的toString:返回一个字符串,内容为"[object Object]"
显示类型转换规则
1.转换为Boolean
除了undefined、null、""、NaN、false、0所有都为真。
2.转换为Number
---Boolean---
true => 1
false => 0
---
null => 0
---
undefined => NaN
---String---
全部为数字的字符串将会正常转换成数字,其中包括正负号和第一个小数点。如果为0开头,将忽略0。
空字符串 "",空格 ' ',换行符'\n',制表符'\t' => '0'
剩下的字符串都转换为NaN
---Object---
如果有valueOf优先调用valueOf方法,返回的如果是基本类型那么转换成数字返回。
然后如果有toString方法再调用toString方法,返回的如果是基本类型那么转换成数字返回。
以上如果都没成功,那么报错。
var obj1 = {
say : function(){
return 123;
}
} //这个对象没有valueOf方法所以执行了Object原型上的toString方法,返回的是"[object Object]",然后这个值转换成数字是NaN。
var obj2 = {
valueOf : function(){
console.log("执行了valueOf");
return 123;
},
toString : function(){
console.log("执行了toString");
return 345;
}
}
var obj3 = {
toString : function(){
console.log("执行了toString");
return 345;
}
}
//以上两个函数都正常输出了,并且是遵循的先执行valueOf后执行toString的顺序。
var obj4 = Object.create(null); //这个对象没有toSring方法,所以转换成数字会报错。
3.转换成String
---Object---
跟转换成数字一样,只不过顺序反过来
如果有toString优先调用toString方法,返回的如果是基本类型那么转换成字符串返回。
然后如果有valueOf方法再调用valueOf方法,返回的如果是基本类型那么转换成字符串返回。
以上如果都没成功,那么报错。
隐式类型转换
只有以下几种情况才会发生隐式类型转换:
isNaN()
//检查参数执行Number()转换后是不是NaN
++、-- +、- (一元正负)
//先调用Number()再计算,返回数字类型
+
//如果在两侧发现字符串那么就调用String()方法全转换成字符串再拼接。
- * / %
//先调用Number()再计算,返回数字类型
&& || !
//会先将表达式的左右两侧转换成Boolean值再做比对。
< > <= >=
//先调用Number()再比对,返回数字Boolean
//如果两侧都为字符串,那么比对字符串的ASCII码。
== !=
//undefined == undefined, undefined === undefined
//null == null, null === null
//undefined == null
//以上全为真
//NaN不等与任何值,转换后也不等于
//[] == ![] 返回true,因为[]会被toString()转换成空字符串"",![]会先做Boolean([])转换为true,然后再做对比。"" == !true的结果是true。
*不发生隐式类型转换
=== !==
=================================
2.逻辑运算符
逻辑运算符操作顺序 权重表(其中“。”代表值的位置)
20 --- () (括号运算符,拿不准的都可以括起来)
19 --- . (属性访问 obj.xxx)
--- [] (属性访问 obj[xxx])
--- new fun() (带参数列表)
--- fun() (函数调用)
18 --- new fun() (无参数列表)
17 --- 。++ (后置递增 1++)
--- 。-- (后置递减 1--)
16 --- !。 (逻辑非)
--- ~。 (按位非)
--- +。 (一元加法)
--- -。 (一元减法)
--- ++。(前置递增 ++1)
--- --。(前置递减 --1)
--- typeof 。(取值类型 typeof xxx)
--- delete 。(删除对象的某个属性)
--- await 。(同async一起处理异步)
15 --- 。** 。 (幂, 2**4就是2的4次幂)
14 --- 。* 。 (乘法)
--- 。/ 。 (除法)
--- 。% 。 (取模)
13 --- 。+ 。 (加法)
--- 。- 。 (减法)
12 --- 。<< 。(按位左移)
--- 。>> 。(按位右移)
--- 。>>> 。(无符号右移)
11 --- 。< 。(小于)
--- 。<=。(小于等于)
--- 。> 。(大于)
--- 。>= 。(大于等于)
--- 。in {} (in运算符,如果左侧 值 存在于右侧对象或其原型链属性中,则返回true,反之返回false)//重点在值
--- {} instanceof {} (instanceof运算符,左侧对象的原型链中如果有右侧对象,则返回true,反之返回false)//重点在对象
10 --- 。== 。(等号)
--- 。!= 。(非等号)
--- 。=== 。(全等号)
--- 。!== 。(非全等号)
9 --- 。& 。(按位与)
8 --- 。^ 。(按位异或,离散数学的知识,这时候体现出知识短浅了)
7 --- 。| 。(按位或)
6 --- 。&& 。(逻辑与)
5 --- 。|| 。(逻辑或)
4 --- 。? 。: 。(三目运算符)
3 --- 。= 。(赋值运算符,包括 +=、-=...等)
2 --- yield 。(Generator迭代器的操作符,具体请见es6)
--- yield* 。(Generator迭代器的操作符,具体请见es6)
1 --- ... 。(展开运算符,具体请见es6)
0 --- 。, 。(逗号运算符。表达式中的运算规则是只算后面的值)
//逗号运算符扩展,es3中是允许函数传相同参数的,例:function(a,a){console.log(a)}; 但是,如果只传了一个实参,那么会被认为是第二个,而不是常规意义上会理解成的第一个,所以当前运算的结果是undefined
//es5如果传两个相同的参数会报错
=================================
1.作用域、作用域链([[scope]])
自我理解:window有自己的GO,该GO中存有所有在window中的函数与变量声明和一个指向window的this。函数声明会产生一个作用域AO,这个AO会建立在父级函数的AO之上,这个AO就是该函数的作用域。如果该函数是在window中声明,那么它的AO将会建立在GO之上。函数中如果访问不到指定变量,将会向父级AO访问该变量,直到GO如果还访问不到,将会返回undefined。这一访问过程是通过链式访问的,这个访问的链就是作用域链。
预编译环节,函数的this指向window。当函数被调用时,obj.fun()该函数的this指向它的调用者obj,当函数没有调用者直接执行时fun(),this指向window。
正确讲解:
[[scope]]:每个javascript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供javascript引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫作用域链。
2.立即执行函数
定义函数的方式有两种:一种是函数声明,另一种是函数表达式;
函数声明:function hello(){};
函数表达式:var hello = function(){};
函数表达式看起来像变量赋值,这种情况下创建的函数叫做匿名函数,因为function关键字后面没有标识。函数表达式与其他表达式一样,使用前需要先赋值,否则将会报错。例如:
hello();
var hello = function(){}
立即执行函数就是通过表达式的形式调用函数,该函数一定是一个匿名函数:
var hello = function(){}()
标准写法为:
(function(){}())或(function(){}()
变形写法:
+function(){}(),-function(){}(),!function(){}(),0 == function(){}(),0&&function(){}()等...
只要能形成表达式的写法都可以匿名自调,但是有些符号不被识别成表达式,如:function(){}(),/function(){}(),%function(){}(),运算表达式需要填完整运算符两边的数值,如:0function(){}()
延伸:
function foo(){}()会报错
但是:function foo(){}(1,2,3,4)不会报错,因为系统会认为这是一个函数声明和一个括号表达式,而不是函数的调用传参。
3.闭包
自我理解:闭包就是通过将函数内部函数保存到外部的形式访问函数内部变量。例:
function foo(){
var test = 0;
return function(){
console.log(test)
}
}
foo()()//结果为 0
变形:
var obj = {}
function foo(){
var test = 0;
obj.say = function(){
console.log(test)
}
}
foo();
obj.say();
变量的销毁机制:
全局变量只有在关闭页面或关闭浏览器的时候才会被销毁,局部变量会在该函数执行完成后,该局部变量没有再次使用的时候就会被销毁。
闭包就阻止了函数内部变量的销毁,从而使该变量一直存储在内存中(等价于全局变量)。
闭包的形成:
在外部函数执行后,内部函数将被声明,在内部函数没有执行的情况下,内部函数一直保有外部函数的AO,该AO中就有外部函数所定义的变量。当内部函数被保存到外部后,该函数所保有的外部函数的AO和自己的声明AO也会被保存到外部。所以就形成了外部函数已经执行完毕但该函数的变量还可以访问的情况。
形成闭包的影响:
因为被销毁的函数的变量一直没有被销毁,闭包将会造成内存无故被占用,也就是内存泄露。
闭包的应用:
实现公有变量:不依赖于外部变量,并且可重复执行的函数累加器。
私有化变量:通过闭包的形式使外部不可访问内部变量,从而避免变量命名冲突等情况。
数据的缓存机制:通过闭包的形式使变量隐式的保存在某个函数体内部。当某个运算被重复执行的时候可以应用缓存机制。
例如:function(a,b){
var sum = a+b;
return sum;
}
该函数的a和b都会有数值重复的情况,如传了两次1和2,也就是算过一次1+2后又算了一次甚至更多次1+2,这个时候就可以通过访问缓存直接拿到算好后的数值,而省去计算的步骤。
闭包的变形
function fn(){
var arr=[];
//i为fn函数中的局部变量。
for(var i=0;i<3;i++){
arr.push(function(){
return i;
});
}
return arr;
}
var b=fn();
for(var i=0;i<b.length;i++){
console.log(bi);//3
}
想要通过push的形式将每次循环的i返回到外部,但结果是输出的三个3。原因是因为bi的时候fn已经执行完毕了,相应的for循环也已经执行完毕了,这时候再访问内部的i变量就是执行后的结果。
改为想要的版本:
function fn(){
var arr = [];
for(var i = 0;i < 3;i++){
(function(j){
arr.push(function(){
return j;
})
}(i))
}
return arr;
}
var b=fn();
for(var i=0;i<b.length;i++){
console.log(bi);//1,2,3
}
或
function fn(){
var arr = [];
for(var i = 0;i < 3;i++){
arr.push(function(j){
return function(){
return j;
}
}(i))
}
return arr;
}
var b=fn();
for(var i=0;i<b.length;i++){
console.log(bi);//1,2,3
}
第一版:改完之后立即执行函数就会将for循环的i保存到立即函数内部,从而保证每次循环都会保存一个相应的i。
第二版:通过匿名函数的立即执行,将立即执行后返回的函数直接赋值给数组arr。每次循环即将i的值传递给num,又因为num在函数中,所以有自己的独立作用域,因此num得到的值为每次循环传递进来的i值,即0,1,2
4.对象、包装类
对象:
一切皆对象;
对象中的变量叫属性,对象中的函数叫方法
obj = {
name:"111",//属性
sayName:function(){//方法
console.log("aaa")
}
}
访问未定义的函数属性将会返回undefined而不会报错。
对象的创建方法
1)字面量:obj = {};
2)Object.create:
var obj = {name:"111"};
var obj1 = Object.create(obj);
3)构造函数:
系统自带的:new Array()/new String()/new Number()/new Boolen()/new Object()/new Date()
自定义的,自定义构造函数首字母需要大写。
自定义构造函数:
function Person(){
this.name = "111";
this.sayName = function(){
reurn this.name;
}
}
var per = new Person();
per.sayName()// "111"
构造函数内部原理:
1.在new了函数之后会隐式的声明一个对象:
var this = {proto:Person.protype}
2.执行函数中this赋值语句
3.将this隐式的return回来
注意:可以显式的手动return {}来替换隐式的this,但是如果return的是原始值将不会被执行。
包装类:
new Array()/new String()/new Number()/new Boolen()
包装类是可以添加属性的,而原始值本身不能添加属性。
包装类的数字可以参与运算,但参与运算后将变成原始值而不再是包装类。
给原始值添加属性和方法不会报错,是因为系统会隐式的将原始值转换成包装类,然后再添加属性和方法。但是,系统添加完后就把该包装类删除掉了。同理,如果访问原始值的属性和方法,系统还是会按以上方法再操作一次。举例:
var str = "abcd";
str.length = 2;
//new String('abcd').length = 2; delete 没有改变原字符串
console.log(str)//'abcd';
console.log(str.length)//4;
题:
var str = "abc";
str += 1;
var test = typeof(str);
if(test.length == 6){
test.sign = 'typeof的返回结果可能为String';
}
console.log(test.sign);//undefined
因为string原始值不能有属性
5.原型,原型链
自我理解:原型(prototype)是对象的构造函数的父级对象,里面包含了该构造函数创建的对象所能使用的属性和方法。当一个对象自我没有指定的属性和方法时,会向它的构造函数上的原型上查找,如果没有,继续向上查找,查找所经过的这条链式结构叫原型链。
原型:
官方解释:
1)定义:原型是function对象的一个属性,它定义了构造函数制造的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
2)利用原型特点和概念,可以提取共有属性
3)对象如何查看原型->隐式属性__proto__
4)对象如何查看对象的构造函数->constructor
例:
Person.prototype.lastName = "jin";
function Person(){
this.name = "aa"
}
var person = new Person();
console.log(person.name,person.lastName)//"aa","jin"
原型属性的增、删、改、查。
构造函数构造出的对象不可修改原型的属性和方法。但可以向原型上的引用值属性赋值并更改属性。
constructor(构造器): 会返回对象的构造函数,对象的constructor是从构造函数的prototype中继承来的,constructor可以手动改变指向
proto: 将对象和原型链接到一条链上,对象需要通过__proto__访问对象的prototype,__proto__可以手动修改。但是,Object.create(null)创建的对象手动添加__proto__是不可以的。
Person.prototype.name = "aa";
function Person(){
}
var person = new Person();
Person.prototype.name = "bb";
console.log(person.name)//"bb";
变形:
Person.prototype.name = "aa";
function Person(){
//var this = {
// proto:Person.prototype
//}
}
var person = new Person();//new的这一刻在构造函数中创建了this
//已经创建了this后prototype的空间转向了新的空间,但person的__proto__仍指向原来的那个空间
Person.prototype = {
name = "bb"
}
var person1 = new Person();
console.log(person.name)//"aa";
console.log(person1.name)//"bb";
再变形:
Person.prototype.name = "aa";
function Person(){
//var this = {
// proto:Person.prototype
//}
}
Person.prototype = {
name = "bb"
}
var person = new Person();//new的这一刻才会在构造函数中隐式的创建this
console.log(person.name)//"bb";
原型链:
例:
Container.prototype.lastName = "Jin";
function Container(){
}
var container = new Container();
Box.prototype = container;
function Box(){
}
var box = new Box();
Item.prototype = box;
function Item(){
}
var item = new Item();
console.log(item.lastName)//"Jin";
一切皆对像
除Object.create(null)创建的对象以外,所有的对象最终都继承自Object.prototype,换句话说,除Object.create(null)创建的对象以外所有对象都有toString()方法。但是,有些包装类有自己的toString(),例如number的toString()就是系统重新定义后的。
方法的重写:
Object.prototype.toString 是系统自定义的方法。
Person.prototype.toString = function(){
return "123";
}
function Person(){}
var person = new Person();
person.toString();//"123"
系统自带的是://"[object Object]"
document.write()方法内部原理是先拿出括号里的值调用toString()方法,然后将toString()完成后的值在页面上呈现出来。
例:
var obj = Object.create(null);
obj.toString = function(){
return "aaaaaaa"
}
document.write(obj);
//结果会把“aaaaaaaa”展现到页面上。
6.call/apply/bind
自我理解:call、apply和bind都能改变函数中的this指向,call和apply是将this和参数传入到指定函数中并执行,bind是改变this指向后等待执行。call和apply的区别是call通过单独变量传参,apply通过数组传参。
引申阅读:bind和on的区别,bind后只能传事件和事件触发后的函数,而on可以在事件的后面传一个selector参数,该参数是可选参数。selector参数可以完成事件委托。
官方文档:
1)作用,改变this指向
2)区别,this后传参形式不同
例:
function Person(name,age){
//this = obj
this.name = name;
this.age = age
}
var person = new Person('jin', 100);
var obj = {
}
Person.call(obj,'jin2',200)//这条语句相当于把Person中的this改成obj
构造函数中的call
function Person(name,age,tel){
this.name = name;
this.age = age;
this.tel = tel;
}
function Student(name,age,tel,taller,width){
Person.call(this,name,age,tel)
// this.name = name;
// this.age = age;
// this.tel = tel;
this.taller = taller;
this.width = width;
}
var student = new Student("aa",123,"2324",34,555)
7.继承模式、命名空间、对象枚举(对象遍历)
自我理解:继承是将一个构造函数的prototype上的属性和方法转嫁到另一个构造函数的prototype上。继承有三种模式:前两个我忘了,最后一个是圣杯模式继承,思路是在一个匿名函数中声明一个中间函数F,通过target继承F,F继承origin的方式继承:
//圣杯继承标准模式(我自己想的)
function inherit(Target,Origin){
F.prototype = Origin.prototype;
function F(){};
Target.prototype = new F();
Target.prototype.construstor = Target;
Target.prototype.uber = Origin.prototype;
}
B.prototype.name = "aaaa";
function A(){}
function B(){}
inherit(A,B)
var a = new A();
命名空间没好好听课,需要重点复习
对象枚举(遍历)
for in
温馨提示:for of不能用来遍历普通对象
例:
var ooo = {
name:"111",
age:222,
home:"哈哈"
}
for(key in ooo){
console.log(key)//打印对象的属性
console.log(ooo[key])//打印对象的内容
}
官方文档:
继承发展史(四种模式)
1)传统形式->原型链
缺点:过多的继承了没用的属性
举例:
Grand.prototype.lastName = "Deng";
function Grand(){
}
var grand = new Grand();
Father.prototype = grand;
function Father(){
this.name = 'aaa';
}
var father = new Father();
Son.prototype = father;
function Son (){
}
var son = new Son();
//son本来想继承Grand上的lastName的属性,但是也把不需要的父级元素上的name继承下来了
2)借用构造函数
缺点:不能继承借用构造函数的原型
每次构造函数都要多走一个函数
举例:
function Person(name, age, sex){
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name, age, sex, grade){
Person.call(this);
this.grade = grade;
}
var student = new Student();
//不能继承原型
//在运行中,多调用了一个函数,对性能优化不利
3)共享原型
缺点:不能随意改动自己的原型
举例:
Father.prototype.lastName = "Deng";
function Father(){}
funcrion Son(){}
Son.prototype = Father.prototype;
var son = new Son();
var father = new Father();
son.lastName//"Deng"
father.lastName//"Deng"
Father.prototype.lastName = "Jin";
son.lastName//"Jin"
father.lastName//"Jin"
//当Father改变自己的prototype后,Son的prototype也会跟着改变。
4)圣杯模式
function inherit(Target, Origin){
function F(){};
F.prototype = Origin.prototype;
//注意:一定要先改指向再new,因为new出来后再改就晚了。
Target.prototype = new F();
//constructor继承错误:
//Target.proto-->new F().proto-->Father.prototype
//所以我们手动改变constructor的指向
Target.prototype.constrctor = Target;
//真正继承的原型(超类)需要手动命名
Target.prototype.uber = Origin.prototype;
}
雅虎模式(高大上)
var inherit = (function(){
var F = function(){}
return function(Target, Origin){
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constrctor = Target;
Target.prototype.uber = Origin.prototype;
}
}())
//闭包的私有化变量应用,上面例子中var F就是个私有化变量,会跟随立即执行函数一起被隐藏。注意:var F = function(){}和function F(){}都是函数,都可以正常操作F.prototype,也都可以new F(),也都可以正常调用F();
命名空间
管理变量、防止全局污染、适用于模块化开发
例:
//命名空间,老办法(新办法是webpack)
var org = {
department1 : {
jicheng : {
name : "abc",
age : 123
},
xumingDeng : {
name : "cvb"
}
},
department2 : {
zhangsan : {
},
lisi : {
}
}
}
org.department1.jicheng.name
var jicheng = org.department1.jicheng;
jicheng.name;
//闭包的私有化变量方法
var name = "aaaa"
var init = (function(){
var name = 'abc';
function callName(){
console.log(name)
}
return function(){
callName();
}
}())
init();
对象的枚举(遍历 enumeration)
for in
1.hasOwnProperty
2.in
3.instanceof
举例:
var obj = {
name : "abc",
age : 123,
home : "lll"
}
for(prop in obj){
//如果不是原型链上的属性会返回true
if(obj.hasOwnProperty(prop)){
console.log(obj[prop])
}
}
//obj.prop ---> obj["prop"];
8.this笔试题讲解
this指向问题
@ 全局作用域里 this -> window
@ 函数预编译过程中正常情况下 this -> window
@ 函数预编译过程中"use strict"模式下,this为undefined
@ call/apply可以改变函数运行时this指向
@ obj.func();func()里面的this指向obj
@ 构造函数中this指向构造出来的对象
@ (es6)箭头函数中的this会指向this所在的当前作用域的父级作用域
1)
var x = 1,y = z = 0;
function add(n){
return n = n+1;
}
y = add(x);
function add(n){
return n = n+3;
}
z = add(x);
问xyz分别是啥?
//预编译问题
最后 x=1,y=4,z=4;
2)
function foo(x){
console.log(arguments)
}
foo(1,2,3,4,5)
//arguments和实参列表相映射问题
3)
function foo(){
bar.apply(null, arguments);
}
function bar(){
console.log(arguments);
}
foo(1,2,3,4,5)
//考的apply调用问题
4)
parseInt(3,8)//3
parseInt(3,2)//nan
parseInt(3,0)//nan 或 3
//以后面进制转换前面数字,2进制里没有3所以返回nan
5)
//逗号操作符会返回逗号后面的值,例:var num = (1,3) 返回3。注意:var num = 1,3会被识别成声明变量而报错。
var f = (
function f(){
return "1";
},
function g(){
return 2;
}
)()
typeof f;//number
6)
var x = 1;
if(function f(){}){
x+= typeof f;
}
console.log(x)
//这道题很重要,考点覆盖较多
首先:typeof返回的结果是字符串,然后:if后面的(function f(){})会被当成表达式执行,执行后f就不存在了,所以下方在访问f的时候f已经执行完并被释放掉了,所以会返回字符串“undefined”,所以最后结果是“1undefined”。
7)
下面哪些表达式的结果为true?
A、undefined == null
B、undefined === null
C、isNaN("100")
D、parseInt("1a") == 1
延伸:手写NaN
function myIsNaN(num){
var ret = Number(num);
ret += "";
if(ret == "NaN"){
return true;
}else{
return false;
}
}
//因为 NaN != NaN,所以转换成字符串再比对
8)
//一个综合性很强的题
var name = "222";
var a = {
name : "111",
say : function(){
console.log(this.name);
}
}
var fun = a.say;
fun();//"222"
new fun();//undefined
a.say();//"111"
var b = {
name : "333",
say : function(fun){
fun();
}
}
b.say(a.say);//"222"
b.say = a.say;
b.say();//333
9)陷阱题
var foo = 123;
function print(){
this.foo = 234;
console.log(foo);
}
print();
//注意:这种题目一定要看全,不能盲目自信;
//首先:打印的foo确实是window上的,函数自己没有。然后:函数没有new,而是直接执行,就意味着函数中的this指向了window。最后函数通过this指向window的方式改变了window上foo的值。
10)
var bar = {a:"002"};
function print(){
bar.a = "a";
Object.prototype.b = "b";
return function inner(){
console.log(bar.a)//a
console.log(bar.b)//b
}
}
//这里打印a是因为函数里的赋值操作覆盖掉了window上的值,类似于全局污染。
print()();
变形
var bar = {a:"002"};
function print(){
var bar = {
a:'a'
}
Object.prototype.b = "b";
return function inner(){
console.log(bar.a)//a
console.log(bar.b)//b
}
}
print()();
//这里打印的a是因为闭包将函数体中的AO保存出来了,没有用到window中的值
再变形
var bar = {a:"002"};
function print(){
var bar = {
a:'a'
}
Object.prototype.b = "b";
return function inner(){
console.log(this.bar.a)//002
console.log(bar.b)//b
}
}
print()();
//这里会打印002是因为函数在直接调用的情况下this指向window
9.arguments、克隆(深度克隆)
自我理解:arguments为函数参数的实参列表的映射,arguments是一个类数组。arguments有一个属性:arguments.callee返回当前正在执行的函数,一般配合匿名函数使用。克隆:浅度克隆是将一个对象的属性和方法完全复制到另一个对象上的操作。但这样克隆有一个弊端,被克隆的对象如果有引用值属性,那么该对象在更改引用值属性的时候,克隆所使用母体中的同一个引用属性也会跟着发生改变。为了解决以上弊端,我们采用深度克隆。深度克隆可以保证两个对象无论怎么更改属性和方法都不会互相影响。思维:采用递归。
深度克隆:
var obj = {
name:"abc",
age : 123,
sex : "female",
card : ["visa","aaa","bbb",[1,2,4]],
play : {
bask : "llll",
go : {
kkk:'poi'
}
},
say : function(){
console.log("ooo")
}
}
function clone(origin, target = {}){//es6的函数参数默认值
//var target = target || {}
for(var prop in origin){//循环目标对象每一位
//第一次手写丢掉了这里,判断是否是自身属性而不是原型链上的属性
if(origin.hasOwnProperty(prop)){
if(typeof(origin[prop]) != "object" || origin[prop] == null){//如果是原始值或者null,那么正常复制
target[prop] = origin[prop];
}else{
target[prop] = origin[prop] instanceof Array ? clone(origin[prop],[]) : clone(origin[prop])//如果是数组或对象,那么需要以当前属性为基准对象再做一次克隆
// target[prop] = origin[prop].constructor == Array ? clone(origin[prop],[]) : clone(origin[prop])
// target[prop] = Object.prototype.toString.call(origin[prop]) == ['object Array']? clone(origin[prop],[]) : clone(origin[prop])
}
}
}
return target
}
var obj1 = clone(obj)
官方:
arguments.callee
当立即执行函数在调用自己的时候,只能用arguments.callee来表示自己。例:
//求100的阶乘
var num = (function(n){
if(n == 1){
return 1;
}
return n * arguments.callee(n - 1);
}(100))
/////
function test(){
console.log(arguments.callee);//指向的是test
function demo(){
console.log(arguments.callee);//指向的是demo
}
demo();
}
test();
fun.caller
function test(){
demo();
}
function demo(){
console.log(demo.caller)//指向test
}
test()
注意:arguments.callee和fun.caller在“use strict”模式中都不可以使用
克隆:(浅度克隆)
var obj = {
name:"abc",
age : 123,
sex : "female",
//card : ['visa','aaa','bbb',["00","88"]]//这里的克隆会都指向同一个引用,是错误的,需要深度克隆
}
function clone(origin, target = {}){//此处是es6用法
for(var prop in origin){
target[prop] = origin[prop];
}
return target
}
var ooo = clone(obj)
深度克隆
//遍历对象 for(var prop in obj)
//1.判断是不是原始值 typeof()是不是object
//2.判断是数组还是对象 三种方法:instanceof/toString/constructor
//3.建立相应的数组或对象
//递归
var obj = {
name:"abc",
age:123,
card:['visa','master'],
son:{
name:"bdc",
son:{
name:"aaa"
}
}
}
function deepClone(origin, target){
var target = target || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for(var prop in origin){
if(origin.hasOwnProperty(prop)){
if(typeof(origin[prop]) == 'object' && origin[prop]!== 'null'){
target[prop] = toStr(origin[prop]) == arrStr ? [] : {};
deepClone(origin[prop],target[prop]);
}else{
target[prop] = origin[prop];
}
}
}
return target;
}
总结后克隆:
/*
思路:
1.遍历原对像
2.区分是否是自身属性origin.hasOwnProperty(prop),如果为true说明是自身属性,如果为false说明是继承属性
3.用typeof()区分值类型是否是“object”类型,如果不是直接复制并return target,如果是跳往第四步
4.用三种方法区分是不是数组(originp[prop] instanceof Array/origin.constructor == Array/Object.prototype.toString.call(origin[prop]) == "[object Array]")
5.如果是数组那么以现在的属性为origin再次调用deepClone并在第二个参数中传入一个空数组,反之还是调用deepClone并不传入第二个参数或传个空对象
6.简化代码,把if else转换成三目运算
*/
//简化前
function deepClone(origin,target = {}){
for(prop in origin){
if(origin.hasOwnProperty(prop)){
if(typeof(origin[prop]) == "object"){
if(origin[prop] instanceof Array){
target[prop] = deepClone(origin[prop],[])
}else{
target[prop] = deepClone(origin[prop])
}
}else{
target[prop] = origin[prop];
}
}
}
return target;
}
//简化后
function deepClone(origin,target = {}){
for(prop in origin){
origin.hasOwnProperty(prop) && (target[prop] = typeof(origin[prop]) == "object" ? (origin[prop] instanceof Array ? deepClone(origin[prop],[]):deepClone(origin[prop])):target[prop] = origin[prop]);
}
return target;
}
10.三目运算
三目运算是if else的简化写法,并且可以在判断后返回值。
条件 ? 如果条件为真返回这个 : 如果条件不为真返回这个
举例:
var num = 1 > 2 ? 1 : 0
//结果 num = 0;
延伸题:
var num = 1 > 0 ? ("10" > "9" ? 1 : 0) : 2;
//此时会返回 0 ,因为字符串相比较会逐一按照字符串的ASII码对比的,“1”不大于“9”。
11.数组、类数组
数组的定义方式:
var arr = []//数组字面量
var arr = new Array()//系统提供的构造函数
数组的所有方法都来源于Array.prototype
new Array(10.2) 声明的时候如果只有一位,那这一位不能填小数,因为他会认为你在访问数组的第10.2位,这样就会报错。
当你访问数组没有的索引时,会返回undefined。当向数组的第n位赋值的时候,系统会把该数组的length加长到n+1,同时将没有值的索引用undefined填充。
数组的常用方法(es3/es5)
1)改变原数组方法
push(),pop(),shift(),unshift(),sort(),reverse(),splice()
2)不改变原数组方法
concat(),jion()&split(),toString(),slice()
- push()方法:向数组最后一位追加任意数量的值,不仅限于1位。可以手动覆盖系统原方法。
手动实现:
Array.prototype.push = function(){
for(var i=0;i<arguments.length;i++){
this[this.length] = arguments[i];
}
return this.length;
}
pop()方法:把数组中的最后一位剪切掉,并返回。
unshift()方法:向数组中首位添加任意数量的值,不仅限于1位。可手动覆盖系统原方法。
shift()方法:把数组中的第一位剪切掉,并返回。
reverse()方法:把当前数组中的元素排列顺序反转。
splice()方法:接收3个参数,第一个为当前数组需操作的索引位(包括当前索引位)(可接收负数,负几就是从倒数第几位开始),第二个参数为在该位置向后截取的长度(该参数不接收负数参数,如果参数为负数,不报错但也没有实际意义,必填),第三个和以后的参数为在该位置想要添加的值,相当于是在该位置调用push方法(如果第二个位置没有意义,比如“0”,负数,空字符串等,那么就直接添加而不剪切)。该方法最后会把剪切后的值重新组成一个数组并返回。
splice方法也被称为数组瑞士军刀方法。sort()方法:在原数组上按照升序的办法排序。但是,原生方法的比较办法是讲数组中的值转换成字符串后再比较,所以两位数的数字参与排序会不准。
该方法接收一个匿名函数作为参数,函数中必须传入两个形参。当函数执行后return值为负数,那么第一个参数排在第二个参数的前面,反之则排在后面,返回0不改变位置。
实际应用;
var arr = [1,3,5,2]
arr.sort(function(a,b){
return a - b;//1-3为负数,所以是升序
//return b - a;//3-1为正数,所以是降序
})
console.log(arr)//[1,2,3,5]
面试题:给一个有序的数组进行乱序操作
var arr = [1,2,3,4,5,6,7]
arr.sort(function(a,b){
return Math.random() - 0.5
})
---不改变原数组---
concat()方法:该方法将该方法的调用数组拼接到参数数组的前面,并返回。举例:var arr = [1,2,3].concat([4,5,6])
console.log(arr)//[1,2,3,4,5,6]toString()方法:该方法由系统自身重写覆盖了Object方法,将数组直接转换成字符串。举例:var arr = [1,2,3,4].toString();
console.log(arr)//"1,2,3,4"slice()方法:该方法接收两个参数,第一个函数是方法的起始索引(该参数支持负数,如果是负数就倒数截取),第二个参数是截取到的索引值。如果第二个参数为空那么会截取到数组最后。如果两个参数都不写就会截取整个数组(用来将类数组转换成数组)。
join()方法:该方法会将括号里的参数作为连接符将字数组转为字符串
举例:[1,2,3,4,5].jion("!")
结果为"1!2!3!4!5"split()方法:该方法会将括号里的参数作为拆分符将符串转为数组
举例:"1!2!3!4!5".split("")
结果为["1", "!", "2", "!", "3", "!", "4", "!", "5"]
类数组
类数组的基本形态
var obj = {
"0" : "a",
"1" : "b",
"2" : "c",
"length" : 3,
"push" : Array.prototype.push,
"splice" : Array.prototype.splice
}
类数组的特点
1.属性中必须有length属性
2.属性要为(数字)索引
3.最好有push方法,当加入了splice方法后外观就会跟数组一样
面试题:
var obj = {
"2" : "a",
"3" : "b",
"length" : 2,
"push" : Array.prototype.push
}
obj.push('c');
obj.push('d');
//该题主要考对push()的理解和对类数组的理解,因为push()的内部原理是在数组的第length位加入指定变量,并让length++,而当前类数组的length是2,那么系统就会在第2加入'c',也就是原来的'a'改成了'c'。完成后length会变成3,那么下一个push执行的时候将会操控该数组的第3位,也就是原来的'b'变成了'd'。所以最后该类数组将变成:
var obj = {
"2" : "c",
"3" : "d",
"length" : 4,
"push" : Array.prototype.push
}
作业:
封装一个type函数,函数的作用是当传入原始值就返回原始值类型,当传入引用值就返回具体引用值类型,当传入包装类要返回该包装类的‘原始值 Object’。举例:type(123) --> number,type(new Number(123)) --> number Object,type(array) --> array
数组去重(一共有三种实现方法),要求在原型链上编程。
function type(value){
if(target == null){
return null;
}
var callVal = Object.prototype.toString.call(value);
if(typeof(value) == "object"){
if(callVal == "[object Array]"){
return "array"
}else if(callVal == "[object Object]"){
return "object"
}
var arr = callVal.split('');
arr.splice(0,1);
arr.splice(-1,1);
callVal = arr.join("")
return callVal
}else{
return typeof(value)
}
}
成哥思路
//先定义一个模版对象,其中的属性名是对象tostring.call后返回的结果,属性值是我们想要展示的结果。
//判断如果是null那么就返回null。
//如果是“object”那么就对象tostring.call一下,新定一个变量str接收call后的结果,然后返回template[str],这点很重要,对象可以用中括号加变量名的方式访问变量。
//如果是原始值就直接返回。
function type(target){
var template = {
'[object Array]' : "array",
'[object Object]' : "object",
'[object Number]' : "number - object",
'[object Boolean]' : "boolean - object",
'[object String]' : "string - object"
}
if(target == null){
return null;
}
if(typeof(target) == "object"){
var str = Object.prototype.toString.call(target);
return template[str];
}else{
return typeof(target);
}
}
2)//数组去重
----往下看----
12.数组去重(三种基础数组去重)
数组去重
var arr = [12,7,3,3,4,5,56,6,7,78,]
第一种,利用indexof去重
Array.prototype.mysort = function(){
var newarr = [],len = this.length;
for(var i=0;i<len;i++){
if(newarr.indexOf(this[i]) == -1){
newarr.push(this[i])
}
}
return newarr;
//此处在实现时出现了以下错误
//1.函数的调用是通过arr.mysort()调用的,而不是mysort(arr),所以刚开始的传参错误
//2.len = this.length;所以len只能在遍历中代替length不要把len当成this来使用
//3.indexOf的“O”要大写,而且indexOf的调用者是数组,参数才是要检测的变量
}
第二种,排序后[i] != [i-1]的方法
Array.prototype.mysort = function(){
this.sort();
var newarr = [],len = this.length;
for(var i=0;i<len;i++){
if(this[i]!= this[i-1]){
newarr.push(this[i])
}
}
return newarr;
}
第三种,利用对象的属性去重
Array.prototype.mysort = function(){
var obj = {},newarr = [],len = this.length;
for(var i=0;i<len;i++){
obj[this[i]] = "123";
}
for(prop in obj){
//这行原本代码为 newarr.push(prop1) 转换为数字类型
newarr.push(String(Number(prop).toString()1))
}
return newarr;
//此处出现的问题是
//1.对象的属性如果是字符串的话就不能用“.”来调用或赋值了,需要用“[]”来调用或赋值
//2.值类型的隐式类型转换和显示类型转换,字符串没有“str.toNumber()”方法,强制类型转换需要调用“Number(str)”。
}
//成哥思路
Array.prototype.mysort = function(){
var obj = {},newarr = [],len = this.length;
for(var i=0;i<len;i++){
if(!obj[this[i]]){
obj[this[i]] = "123";
newarr.push(this[i])
}
}
return newarr;
}
//区别:这个方法在循环中去判断对象的当前位的值是不是为空,如果为空那就赋个为真的值,并且向新数组中push对象的当前位,反之则不操作。
而我们的想法是先直接给对象赋值,完成后把对象的值遍历后push到新数组里。
复习答疑
//var关键字声明的window上的变量叫做不可配置属性,也就是说不能通过delete关键字删除。
13.try{}catch(e){}、es5标准模式
1)try里的代码可以正常执行,但的报错代码及以后的代码将不再执行,try catch以后的代码将正常执行。try中的错误代码报出的报错信息将会通过参数的形式传到catch中。
try{
console.log("a")
console.log(b)
console.log("c")
}catch(e){
console.log(e)
//ReferenceError: b is not defined
at app.js:750
}
2)错误类型(共6种)
01 EvalError: eval()的使用与定义不一致
02 RangeError: 数值越界
03 ReferenceError: 非法或不能识别的引用值
04 SyntaxError: 发生语法解析错误
05 TypeError: 操作数类型错误
06 URIError: URI处理函数使用不当
3)es5严格模式
一旦启用严格模式"use strict",那么es3和es5冲突的部分将会以es5为主。
"use strict" 可以放在通篇的最首,也可以放在函数体内部,放在页面最首表示全局使用es5,如果是函数内部表示该函数使用严格模式。该声明无论是全局使用还是局部使用都必须放在最顶端,以上可以有空格回车注释字符串之类的,但一定不能有代码。换句话说,"use strict" 之前的任何东西都不能有实际意义。
"use strict" 推荐使用局部内声明。
es5不允许使用的es3:
01 with()
//with的参数是个对象,会把该对象作为内部代码体的作用域链的最顶端(最近的那个,会先查找的那个)。
//因为with的改变作用域太消耗系统的运行,所以es5取消掉了with。
var obj = {
name : "obj"
}
var name = "window";
function test(){
var name = "scope";
with(obj){
console.log(name);
}
}
02 arguments.callee/func.caller也不允许使用
03 变量赋值前必须声明,否则会被认定为错误。
04 局部的this必须赋值,而且是赋值是啥就是啥,不会被转换成包装类。而es3会把原始值封装成包装类。
05 拒绝重复的属性和参数。
4)eval()
//ecal()可以将字符串转换成代码段
function test(){
eval("console.log('aaa')")
}
test();
14.dom初探
什么是dom
1)DOM -> Document Object Model(文档对象模型)
2)DOM定义了表示和修改文档所需的方法。DOM对象即为宿主对象,由浏览器厂商定义,用来操作html和xml功能的一类对象集合。也有人称DOM是对HTML以及XML的标准编程接口。
//dom提供了一些方法,这些方法可以操作html和xml。dom不能直接操作css,dom可以通过行内样式的方法更改标签样式。
//xml中的标签可以自定义,xml也可以做数据传递,数据传递的另一个格式也是现在用的最多的格式是json。
15.dom选择器,节点类型
节点的增删改查
查看元素节点
Id主要用来代表大的container,其他情况基本不需要id,如果处于项目中,不能通过id查找元素。选择器一般只用class。
* document代表整个文档
* document.getElementById()//Id选择器,ie8以下浏览器如果查找不到id将会查找name属性最为替代属性。
** document.getElementsByTagName()//标签选择器,将所有符合条件的标签组成一个类数组返回。全兼容。
* document.getElementsByName()//name选择器,旧版本只有表单标签(input,iframe,img...)可以加name。
* document.getElementsByClassName()//类名选择器,ie9以下的浏览器不兼容。
// 注意:下面两种方法只会静态的获取,如果有通过js apped页面中的元素,将不会被选择。类似拍照片,定了就不能再获取实时增删的了。
* querySelector()//查询选择器,通过css的方式选择 单个 页面元素。能且只能找到第一个符合条件的元素,不形成数组。ie7以下的浏览器不兼容。
* querySelectorAll()//查询选择器,通过css的方式选择 多个 页面元素。会把所有符合条件的元素以一个类数组的形式返回。ie7以下的浏览器不兼容。
//以下代码证明了jquery的底层选择器是querySelector();缺点是只能静态获取,不能实时的跟着发生改变。
// console.log(document.getElementsByTagName("div"))
// console.log($('div'))
// document.body.appendChild(document.createElement("div"))
// console.log($('div'))
总结:
//document.getElementsByTagName()方法是最常用方法,因为全版本浏览器都兼容。
//document.getElementById()和document.getElementsByName()最不兼容。
//document.getElementsByClassName()在ie9以下不兼容,需要处理版本兼容问题。
//querySelector和querySelectorAll只能获取静态的标签,不实时,所以不推荐。
遍历节点树
-----------
注意:下面所有方法都是基于节点的基础上遍历返回的。不仅局限于元素节点。
-----------
//举例:
<div>
<span></span>
<a></a>
<p></p>
</div>
* parentNode //父节点(最顶端的parentNode为document,再往上就没有了,是null)
例:document.getElementById("div").parentNode //注意,此处不能有括号结尾。
//举例:document.getElementsByTagName("a")[0].parentNode //<div></div>
** childNodes //子节点,多个。返回的是一个直系子节点的类数组集合。其中不止包含元素节点,还有诸如注释节点、文本节点等。
//举例:document.getElementsByTagName("div")[0].childNode //[text,span,text,a,text,p,text],共7个。
* firstChild //第一个子节点
//举例:document.getElementsByTagName("div")[0].firstChild //span前面的 text节点
* lastChild //最后一个子节点
//举例:document.getElementsByTagName("div")[0].lastChild //p后面的 text节点
* nextSibling //下一个兄弟节点
//举例:document.getElementsByTagName("a")[0].nextSibling //a 后面的 text节点
* previousSibling //前一个兄弟节点
//举例:document.getElementsByTagName("a")[0].previousSibling //a 前面的 text节点
遍历元素节点树
* parentElement //父元素节点,ie9以下版本浏览器都是不兼容。
** children //返回当前元素的元素子节点
* node.childElementCount === node.child.length //当前元素子节点的个数,ie9以下版本浏览器都是不兼容。
* firstElementChild //第一个元素子节点,ie9以下版本浏览器都是不兼容。
* lastElementChild //最后一个元素子节点,ie9以下版本浏览器都是不兼容。
* nextElementSibling //下一个元素子节点,ie9以下版本浏览器都是不兼容。
* previousElementSibling //上一个元素子节点,ie9以下版本浏览器都是不兼容。
以上所有方法只有children兼容ie,剩下的所有都不兼容ie9以下版本。
节点的类型
* 元素节点 --- 1
* 属性节点 --- 2
* 文本节点 --- 3
* 注释节点 --- 8
* document --- 9
* DocumentFragment --- 11
节点的属性
* nodeName //当前节点的节点名,元素节点的nodeName为大写。只读的,不可增删改查。
* nodeValue //当前节点的取值,可读写操作。只有文本节点和注释节点可读写nodeValue,其他节点都不行。
* nodeType //当前节点的节点类型,只读。
* attributes //元素节点的属性集合,通常的读取操作都会用setAttrbutes和getAttrbutes替代。
节点的方法
* Node.hasChildNodes();//是否含有子节点,返回true或false。
16.dom继承树,dom基本操作
//继承关系树
//在Node之上的是TargetEvent.prototype,再往上就是Object.prototype
- Node:
- Document:
XMLDocument(暂时用不到)- HTMLDocument
document //document节点 9
- HTMLDocument
- CharacterData:
Text //文本节点 3
Comment //注释节点 8 - Element: //元素节点 1
- HTMLElement:
HTMLHeadElement
HTMLBodyElement
HTMLTitleElement
HTMLParagraphElement
HTMLInputElement
HTMLTableElement
Attr
- HTMLElement:
- Document:
document.proto <=> HTMLDocument.prototype
HTMLDocument.prototype.__proto <=> Document.prototype
Document.prototype //爷爷
HTMLDocument.prototype //爸爸
document //孙子
Dom基本操作
1.getElementById方法定义在Document.prototype上,即Element节点上不能使用
2.getElementsByName方法定义在HTMLDcoument.prototype上,即非html中的document不能使用(XML中的节点不能使用)
3.getElementsByTagName方法定义在Document.prototype和Element.prototype.prototype上。
也就是说document.getElementsByTagName.('div')[0].document.getElementsByTagName.('span')是可以的,选出div下面的span
4.HTMLDocument.prototype定义了一些常用的属性,body,head分别指代HTML文档中的标签。
5.Document.prototype上定义了documentElement属性,指代文档的根元素,在HTML文档中,他总是指代元素。
6.getElementsByClassName、querySelectorAll、querySelector在Document.prototype,Element.prototype类中均有定义。
课堂练习
1).遍历元素节点树(原型链编程)
Node.prototype.printTree = function(name = "div", printTreeArr = []){
let thisTag = this.children, len = thisTag.length, lastRet = "";
for(let i = 0; i < len; i++){
lastRet += thisTag[i].nodeName + ","
if(thisTag[i].children.length > 0){
thisTag[i].printTree(thisTag[i].nodeName, printTreeArr)
}
}
printTreeArr.unshift(name + ":" + lastRet)
return printTreeArr;
}
var tree = div.printTree();
2).封装函数,返回元素e的第n层祖先元素节点
function test(target,num){
for(let i = 0; i < num; i++){
if(target.parentNode.nodeType == 1){
target = target.parentNode
}
}
return target;
}
//成哥答案
function test(elem, n){
while(elem && n){
elem = elem.parentElement;
n--;
}
return elem;
}
3).封装函数,返回元素e的第n个兄弟元素节点,n为正,返回后面的兄弟元素节点,n为负,返回前面的,n为0,返回自己。
//刚开始没有考虑到容错问题,如果传了个100就报错了。改完后首先保证target为真才往后走。
function serchSibling(target,num){
var method = num > 0 ? 'nextSibling' : 'previousSibling';
for(let i = 0; i < Math.abs(num); i++){
target && (target = target[method].nodeType == 1 ? target[method] : target[method][method])
}
return target;
}
4).编辑函数,封装myChildren功能,解决以前部分浏览器的兼容问题。
Node.prototype.myChildren = function (){
if(this.hasChildNodes){
var tar = this.childNodes, len = tar.length, arr = [];
for(let i = 0; i < len; i++){
tar[i].nodeType == 1 && arr.push(tar[i])
}
}
return arr;
}
//基本等于成哥答案,不用改了。
5).自己封装hasChildren()方法,不可用children属性。
Node.prototype.hasChildren = function (){
var tar = this.childNodes, len = tar.length;
for(let i = 0; i < len; i++){
if(tar[i].nodeType == 1){
return true
}
}
return false;
}
//不用改了
增删改查节点
增
document.createElement("div"); //创建元素节点
document.createTextNode("啊啊啊啊"); //创建文本节点
document.createComment("aaaa"); //创建注释节点
document.createDocumentFragment(); //创建文档碎片节点
插
parentNode.appendChild('html内容')
//在元素末端插入指定元素,如果该元素已经存在dom中,那将剪切后再插入。parentNode.insertBefore(a,b)
//在parentNode中,将a插入到b前面。
删
parent.removeChild();
//将parent节点中的指定节点剪切并返回,类似删除。node.remove();
//在dom树中删除当前节点。
替换
- parent.replaceChild(new, origin);
//在parent中用new参数替换掉origin
Element节点的一些属性
innerHTML
//获取或者更改html内容
//ele.innerHTML 获取html内容
//ele.innerHTML = ‘123’ 更改html中的内容,会覆盖原本的内容innerText(老版本火狐不兼容)/textContent(老版本IE不好使)
//获取或更改元素中的文本内容
//注意:也会覆盖原本的html内容
Element节点的一些方法
ele.setAttribute(a, b);
//给该目标元素设置行间属性,a为属性名,b为属性值ele.getAttribute(a);
//获取目标元素的行间属性,a为属性名。
拓展
ele.className 可以直接获取元素的class
ele.className = '123' 可以给元素直接更改className
ele.id 可以直接获取元素的id
ele.id = "123" 可以直接给元素设置id
课后作业
1.封装函数insertAfter(),功能类似insertBefore();
//提示:可以忽略老版本浏览器,直接在Element.prototype上写。
//自己的思路:insertAfter(a, b)
查看a的下一个兄弟元素(nextSibling),如果不为null那么就在下一个兄弟元素的前面插入,如果为null那么就在父级节点appendChild。
//成哥思路
Element.prototype.insertAfter = function(targetNode,afterNode){
var beforeNode = afterNode.nextElementSibling;
if(beforeNode){
this.insertBefore(targetNode,beforeNode);
}else{
this.appendChild(targetNode);
}
}
2.将目标节点内部的节点顺序逆序。
例如:
//自己的思路
var div = document.getElementsByTagName('div')[0]
for(let i=div.children.length-1; i>=0; i--){
div.appendChild(div.children[i])
}
17.date对象,定时器
//系统提供的对象
var date = new Date();
//date对象的方法
date.getDate();//返回当天日期
date.getDay();//返回第几天 0~6,0是周日
date.getMonth();//返回第几月 0~11,0是一月,所以每次算月都要加一
date.getFullYear();//返回当年,四位数字
date.getHours;//返回当前小时
date.getMiutes;//返回分钟
date.getSeconds;//返回秒
date.getMilliseconds();//返回毫秒,0~999
//时间戳
date.getTime();//返回 1970 年 1 月 1 日至今的毫秒数。一般用来做唯一标识
new Date()对象记录了创建当时的时间信息,与调用其方法的时间无关。
定时器
setInterval(fun(),time);循环定时器//会无限循环
setTimeout(fun(),time);一次性定时器//执行一次后就停止
//fun()为执行函数,time为执行间隔,值得注意的是 time只会取一次值,后面怎么写都不会改
例如:
var time = 1000;
setInterval(func(){},time)
time = 2000;//此处time改变了变量的值,但定时器的间隔并没有发生改变
注意:定时器的精度不准
定时器可以命名,例如:
var aaa = setInterval(function(){},200);
清除定时器clearInterval()、clearTimeout();
clearInterval(aaa);//一次性和无限定时器都可以在执行过程中被清除
未命名的定时器会有一个唯一标识,从1开始,第一个是1,第二个是2;
例如:
var timer = setInterval(func(),100);//timer是1
var timer2 = setInterval(func(),100);//timer2是2
定时器全都是window上的方法,所以this指向window
定时器的另一种执行形式
setInterval("console.log('aaa')",2000)//在2000号秒后执行字符串内的代码片段
18.获取窗口属性,获取dom尺寸,脚本化css
1)获取滚动条的滚动距离
window.pageXOffset/pageYOffset(ie8及以下不兼容)
document.body.scrollLeft/scrollTop
document.documentElement.scrollLeft/scrollTop
以上两个的兼容性比较混乱,需要时取两个值的和,因为不会同时存在两个值
封装获取滚动条滚动距离全兼容函数
function getScrollOffset(){
if(window.pageXoffset){
retrun {
x : window.pageXOffset,
y : window.pageYOffset
}
}eles{
retrun {
x : document.body.scrollLeft + document.documentElement.scrollLeft,
y : document.body.scrollTop + document.documentElement.scrollTop
}
}
}
2)查看可视区窗口的尺寸
window.innerHeight/innerWidth(ie8及以下不兼容)
document.documentElement.clientWidth/clientHeight(标准模式下,所有浏览器都兼容)
document.body.clientWidth/clientHeight
标准模式和怪异/混杂模式
存在的情况下为标准模式,否则是怪异模式
document.compatMode 标准模式为CSS1 Compat、怪异模式为BackCompat
封装获取可视区窗口全兼容函数
function getViewportOffset(){
if(window.innerWidth){
return {
width : window.innerWidth,
height : window.innerHeight
}
}else{
if(document.compatMode == "BackCompat"){
return {
width : document.body.clientWidth,
height : document.body.clientHeight
}
}else{
return {
width : document.documentElement.clientWidth,
height : document.documentElement.clientHeight
}
}
}
}
3)查看元素的几何尺寸
domEle.getBoundingClientRect();
兼容性很好
该方法返回一个对象,对象里有left,top,right,bottom等属性。left和top代表该元素左上角的x和y坐标,right和bottom代表右下角的x和y坐标
height和width属性老版本ie并未实现
返回的结果并不是“实时的”
查看元素的尺寸(dom 代指元素节点)
dom.offsetWidth/dom.offsetHeight
查看元素的位置
dom.offsetLeft/dom.offsetTop
对于无定位父级的元素,返回相对文档的坐标。对于有定位父级的元素,返回相对于最近的有定位的父级元素的坐标。
无论该元素是通过margin或是定位都会正常返回所求距离。
dom.offsetParent
返回最近的有定位的父级,如果没有,那么返回body。body.offsetParent为null。
求元素相对于文档的坐标
Element.prototype.getElementPosition = function(){
var widthLong = 0;
widthLong += this.offsetWidth;
if(this.offsetParent.nodeName != "BODY"){
this.parentElement.getElementPosition()
}else{
return widthLong;
}
}
4)使滚动条滚动
window上的三个方法scroll(x,y)、scrollTo(x,y)、scrollBy(x,y)
三个方法功能类似,都是输入xy坐标,实现滚动条滚动。
区别:scrollBy会在之前的数据基础上做累加
举例:利用scrollBy快速阅读的功能
脚本化css
读写元素css属性
dom.style.prop
可读写行间样式,没有兼容性问题,碰到float这样的保留字样,前面应加css。举例:float->cssFloat
复合属性必须拆解,组合单词变成小驼峰式写法。
写入的值必须是字符串格式。
查询计算样式
window.getComputedStyle(ele,null)
//第二个参数正常情况下写null,但是当需要查询伪类样式的时候,填写伪类样式名称即可
计算样式只读
返回的值都是绝对值,没有相对单位
ie8及以下不兼容
ele.currentStyle
计算样式只读
返回的计算样式的值不是经过计算的绝对值
ie独有的属性
封装方法getStyle(ele, prop),获取元素样式,解决兼容性问题
function getStyle(ele, prop){
if(window.getComputedStyle){
return window.getComputedStyle(ele, nul)[prop];
}else{
return ele.currentStyle[prop];
}
}
19.事件
绑定事件处理函数
1.ele.onxxx = function(){}(句柄绑定)
兼容性好,但是一个元素的同一事件上只能绑定一个
基本等同于写在HTML行间上
2.ele.addEventListener(type,function(){},false)
ie9一下不兼容,可以为一个事件绑定多个处理函数
3.ele.attachEvent('on'+type,function(){})
ie独有,可以为一个事件绑定多个处理函数
课件习题:给ul下的三个li分别绑定点击事件,当点击哪个li的时候显示它是几个li
注意点:循环加事件的时候会形成闭包,需要将当前作用域的索引保存好
ele.attachEvent('on'+type,function(){})函数的this并没有指向ele而是window,需要用call解决
ele.attachEvent('on'+type,function(){
tools.call(ele)//将ele作为函数的this传入到函数中
})
function tools(){
//函数执行体
}
解除事件处理函数
ele.onclick = false或''或null;
ele.removeEventListener(type,function(){},false);
ele.detachEvent('on'+type,function(){});
注意:绑定的匿名函数都无法解除
举例:
div.addEventListener('click',function(){
console.log('ooo')
},false)
//以上情况是无法解除事件的,因为函数为匿名函数
div.addEventListener('click',test,false)
function test(){//函数体}
div.removeEventListener('click',test,false)
//以上情况才是正确的解除事件写法
事件处理模型
事件冒泡:
结构上(非视觉上)嵌套关系的元素,会存在事件冒泡,即同一事件,子元素向父元素冒泡。(自底向上)
事件捕获
结构上(非视觉上)嵌套关系的元素,会存在事件捕获,即同一事件,父元素向子元素(事件源元素)捕获。(自顶向下)
触发捕获和触发冒泡的机制:
ele.addEventListener('click',function(){},false)//当前处理模型为 事件冒泡
ele.addEventListener('click',function(){},true)//当前处理模型为 事件捕获
//注意:事件捕获只有在chrome的wekit内核中才能实现,ie没有,新版火狐和欧朋都有
触发顺序:先捕获再冒泡
focus、blur、change、submit、reset、select等事件不冒泡
当事件对象为源对象时,也就是说被点击的元素。触发顺序为谁在前就先触发谁,不存在冒泡或者捕获情况。
取消冒泡和阻止默认事件
取消冒泡:
W3c标准event.stopPropagation();但不支持ie9一下版本;
IE独有的event.cancelBubble = true;//谷歌也实现了
作业:封装取消冒泡函数stopBubble(event);
阻止冒泡事件:
默认事件都包括 表单提交、a标签跳转、右键菜单等。
1.return false;以对象属性的方式注册的事件才生效;
2.event.preventDefault;W3c标准,ie9一下不兼容;
3.event.returnValue = false;兼容ie;
作业:封装阻止默认事件的函数cancelHandler(event)。
右键出菜单事件:document.oncontextmenu = function(){};
事件对象
event||window.event只用于ie
事件源对象:
event.target 火狐只有这个
event.srcElement ie只有这个
以上两个chrome都有
兼容性写法
事件委托:
利用事件冒泡和事件源对象进行处理
优点:
1.性能高,不需要循环所有元素一个个绑定事件
2.灵活性高,动态添加新子元素不需要重新绑定事件
举例:
- 1
- 2
- 3
点击哪个li就显示哪个li的文本内容
普通写法:(浪费效率,且不动态)
var li = document.getElemetsByTagName('li'),
len = li.length;
for(var i=0;i<len;i++){
li[i].onclick = function(){
console.log(this.innerText)
}
}
事件委托写法:
var ul = document.getElemetsByTagName('ul')[0];
ul.onclick = function(e){
var e = e||window.e,
target = e.target||e.srcElement;
console.log(target.innerText)
}
预习作业:
鼠标拖拽功能,写一个小方块,鼠标拽着一块动;
事件分类
鼠标事件
click、mousedown、mousemove、mouseup、contextmenu、mouseover、mouseenter、mouseleave
button属性区分鼠标所摁的是左键、中键还是右键,0、1、2
DOM3标准规定:click事件只能监听左键,只能通过mousedown和mouseup来判断鼠标左右键
如何解决mousedown和click的冲突
键盘事件
keydown keyup keypress
keydown>keypress>keyup
keydown和keyress的区别
keydown可以响应任何键盘按键,keypress只能响应自负类键盘按键
keypress返回ASCII码,可以转换成相应字符
事件分类
文本操作事件
input(输入框中的值发生变化了就会触发input事件,实时触发)、change(输入框的值发生了改变会触发该事件,只有当获得焦点时的值和失去焦点的值不同的时候才会触发)、focus、blur
窗体操作事件(window)
window.scroll window.load(需要等页面所有渲染全部完成后才会触发)
20.json,异步加载,时间线
JSON
JSON是一种数据传输格式(以对象为样本,本质上就是对象,但用途有区别,对象就是本地用的,json是用来传输用的)
JSON.parse(); string -> json
JSON.stringify(); json -> string
异步加载js
文档加载分为domtree和csstree两个,这两个同时组成了randertree,渲染的时候是以深度优先的,也就是说 => => \3c div>等(注意:这里head和body是平级关系,如果广度优先将会head后渲染body)。当出现src的外部资源的时候,会先挂载dom树,同时请求下载。也就是说,加载完成一定在解析之后。\3c /p>
\3c p>reflow 重构(效率低) 任何dom节点的增删改查、宽高、位置变化都会触发重构,页面优化的时候要避免这些。\3c br>
repaint 重绘 比如更改背景颜色,影响比重构要小。\3c /p>
\3c p>js加载的缺点:加载工具方法没必要阻塞文档,过多js加载会影响页面效率,一旦网速不好,那么整个网站将会等待js加载而不进行后续渲染等工作。\3c br>
有些工具方法需要按需加载,用到再加载,不用不加载。\3c /p>
\3c p>异步加载js的三种方法:\3c br>
1.defer异步加载,但要等到dom文档全部解析完才会被执行。只有ie能用,也可以将代码写到内部。\3c br>
例:\3c script src="xxx.js" defer>\3c /script>或\3c script defer>代码段\3c /script>\3c br>
2.async异步加载,加载完就执行,async只能加载外部脚本,不能把js写在script标签里。\3c br>
例:\3c script src="xxx.js" async>\3c /script>\3c br>
以上两种执行时不阻塞页面。\3c br>
3.创建script标签,插入到dom中,加载完毕后callback。\3c /p>
\3c p>//index中的script\3c br>
function loadScript(url,callback) { }
\3c br>
loadScript('tools.js',function() { }
\3c br>
或者\3c br>
//index中的script\3c br>
function loadScript(url,callback) { }
\3c br>
loadScript('tools.js','test')\3c br>
\3c a href="//tools.xn--js-ry2c01ad15mm4b" target="_blank">//tools.js中的代码\3c /a>\3c br>
var sys = { }
(该事件只能用addEventListener监听,不能用on)\3c br>
6.async脚本加载完成并执行、img加载完成后,document.readyState = 'complete'、触发window.onload。\3c /p>
\3c p>3-5阶段就是jquery的$(document).ready(function() { }
FE知识点(硕哥)的更多相关文章
- 记录我这一年的技术之路(nodejs纯干货)
2015年12月28日23:19:54 更新koa应用.学习型网站和开发者工具等 coding伊始 开始认认真真的学习技术还是2015.10.21日开始的,记得很清楚,那天,是我在龙湖正式学习的第一天 ...
- muduo库安装
一.简介 Muduo(木铎)是基于 Reactor 模式的网络库. 二.安装 从github库下载源码安装:https://github.com/chenshuo/muduo muduo依赖了很多的库 ...
- java⑿
1.插入: 插入算法: 前提是数组中的数据必须是有序的 public static void main(String[] args) { // 先定义一个int类型的数组 int[] nums = n ...
- 博客和GitHup链接地址
硕哥博客链接:http://www.cnblogs.com/999-/p/6073601.html 硕哥GitHup链接:https://github.com/xiaodoufu
- 《吐血整理》高级系列教程-吃透Fiddler抓包教程(33)-Fiddler如何抓取WebSocket数据包
1.简介 本来打算再写一篇这个系列的文章也要和小伙伴或者童鞋们说再见了,可是有人留言问WebSocket包和小程序的包不会抓,那就关于这两个知识点宏哥就再水两篇文章. 2.什么是Socket? 在计算 ...
- 鸟哥Linux私房菜知识点总结6到7章
近期翻看了一本<鸟哥的Linux私房菜>.这是一本基础的书,万丈高楼平地起.会的不多但能够学.这是我整理的一些知识点.尽管非常基础.希望和大家共同交流. 第6章主机规划与磁盘分区 1.在进 ...
- 鸟哥Linux私房菜知识点总结3到5章
感觉自己对Linux的理解一直不够,所以近期翻看了一本<鸟哥的Linux私房菜>.这是一本基础的书,万丈高楼平地起,会的不多但能够学.这是我整理的一些知识点,尽管非常基础.希望和大家共同交 ...
- 跟着刚哥梳理java知识点——面向对象(八)
面向对象的核心概念:类和对象. 类:对一类事物描述,是抽象的.概念上的定义. 对象:实际存在的该类事物的每个个体,因而也成为实例(Instance). Java类及类的成员:属性(成员变量Field) ...
- 跟着刚哥梳理java知识点——流程控制(六)
分支结构(if…else .switch) 1.if else 语句格式 if(条件表达式){ 执行代码块; } else if(条件表达式){ 执行代码块; } else{ 执行代码块; } 2.s ...
- 跟着刚哥梳理java知识点——运算符(五)
运算符:是一种特殊的符号,用以表示数据的运算.赋值和比较. 1.算数运算符(+.-.*./.%.++.--) a)除: int i = 12; double d1 = i / 5; //2.0 dou ...
随机推荐
- 如何成为10x倍工程师
10倍效率 +10x 的工程师很难找,但是 -10x 工程师是存在的. 所谓 -10x 工程师,就是每周要浪费团队 400 个小时的工程师. 他有以下特征: 创造无效的繁忙工作,比如演示文稿.图表.工 ...
- Java 内存分析(程序实例),学会分析内存,走遍天下都不怕!!!
相信大多数的java初学者都会有这种经历:碰到一段代码的时候,不知该从何下手分析,不知道这段代码到底是怎么运行最后得到结果的..... 等等吧,很多让人头疼的问题,作为一名合格的程序员呢,遇到问题一定 ...
- mybatis复习(二)
简介 mybatis是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql语句本身, 而不需要花费精力去处理加载驱动.创建连接.创建 statement 等繁杂的 ...
- openGauss事务机制中MVCC技术的实现分析
openGauss 事务机制中 MVCC 技术的实现分析 概述 事务 事务是为用户提供的最核心.最具吸引力的数据库功能之一.简单地说,事务是用户定义的一系列数据库操作(如查询.插入.修改或删除等)的集 ...
- 二、Unity调用Xcode封装方法
1.开始封装Unity调用接口 我们在Xcode中 写的接口需要在extern "C"中(加上extern "C"后,会指示编译器这部分代码按C语言的进行编译) ...
- HTC Vive之Unity3d开发日记——手柄交互编程
目录: HTC Vive之Unity3d开发日记 You can fool all the people some of the time,and some of the people ...
- “AI虚拟数字人+线下大屏互动”升级智能人机交互服务!
如今AIGC 强势爆发. ChatGPT 语言大模型横空出世,使得数字人的"大脑"水平得到了极大提升,AI技术赋能下的虚拟数字人拥有了更加精准的语言表达.思考逻辑.帮助各个行业实现 ...
- This version of Android Studio cannot open this project, please retry with Android Studio 4.0 or newer.
前言 遇到的问题,This version of Android Studio cannot open this project, please retry with Android Studio 4 ...
- easyx的使用 鼠标交互(3.1)
本文学习于B站,进行借鉴学习记录: 视频链接:鼠标操作(新版)_哔哩哔哩_bilibili 初始化调用文件头不再使用#include<graphics.h>,选择调用#include< ...
- 如何使用 Serverless Devs 部署静态网站到函数计算(上)
简介:部署个静态网站到函数计算~ 前言 公司经常有一些网站需要发布上线,对比了几款不同的产品后,决定使用阿里云的函数计算(FC)来托管构建出来的静态网站. FC 弹性实例自带的500 Mb 存储空 ...