11、数据类型检测与toString方法的理解
1,typeof value (检测一个值的类型:原始类型或者引用类型)
检测当前值的数据类型,返回的结果:首先是一个字符串,其次字符串中包含了对应的数据类型
[TYPEOF局限]
1、typeof null->"object" null为空对象指针 但是null不是对象数据类型;
2,不能具体细分是数组还是正则还是对象中的其他值,因为使用typeof检测数据类型对于对象数据类型的值返回的都是"object"
BAT面试题:
console.log(typeof typeof []);:
-> typeof [] =>"object"
-> typeof "object" =>"string"
2,A instanceof B
检测A实例是否属于B这个类(可以具体细分一个对象是数组还是正则)A必须是对象数据类型,只要B在A的原型链上出现过,检测结果就是true;
[instanceof局限性]
1,不能用来检测和处理字面量方式创建出来的基本数据类型值;
- 对于基本数据类型来说,字面量方式创建出来的结果和实例方式创建出来的结果是有一定区别的,从严格意义上来讲,只有实例创建出来的结果才是标准的对象,数据类型值也是标准的基本数据类型,也是标准的内置类的实例;对于字面量方式创建出来的结果是基本数据类型的值,不是严格的实例,但是由于JS的松散特点,导致了可以使用 内置类.prototype上提供的方法;
2,只要在原型链上能找到就返回true,所以在类的原型继承中,我们最后用instanceof检测出来的结果未必准确
3,constructor
获取当前实例所属类的构造函数的属性 与instanceof检测类似 可以处理基本数据类型的检测,一般检测不了object数据类型;
[constructor局限性]
1,我们可以把类的原型进行重写,在重写的过程中很有可能出现把之前的constructor给覆盖了,这样检测出来的结果就是不准确的
4,Object.prototype.toString.call().slice(8,-1);
最常用最准确的,借用Object基类原型上的toString方法,在执行这个方法的时候,让方法中的THIS指向需要检测的值,从而获取到当前值所属类的详细信息,进而检测出对应的数据类型
原理:Object.prototype.toString它的作用是返回当前方法的执行主体(方法中的this)所属类的详细信息
var obj={name:"珠峰"};
console.log(obj.toString());toString中的this是obj,返回的是obj所属类的信息
"[object Object]"
- 第一个object代表当前实例是对象数据类型的(固定的)
- 第二个Object代表的是obj所属的类是Object
toString的理解:
1,对于Number、String、Boolean、Array、RegExp、Date、Function原型上的toString方法都是把当前数据类型转换为字符串的类型(它们的作用仅仅是用来转换为字符串的)要把Object类型的值转换为字符串的话可以用JSON.stringify(Object),例如JSON.stringify({name:"珠峰"})-》{"name":"珠峰"}
2,Object.prototype.toString方法并不是用来转换为字符串的({name:"珠峰"}).toString()->"[object Object]"
模拟内置isArray所写检测数据类型的方法:
//根据老师的方法修改版,亲测可用
~function () {
var obj = {
isNumber: 'Number',
isString: 'String',
isBoolean: 'Boolean',
isNull: 'Null',
isUndefined: 'Undefined',
isPlanObject: 'Object',
isArray: 'Array',
isRegExp: 'RegExp',
isFunction: 'Function'
};
var check = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
//给check对象添加每个类的检测方法名和检测方法
check[key] = (function (value) {
return function (val) {
return value===(Object.prototype.toString.call(val).slice(8,-1));
}
})(obj[key]);
}
}
window.check = check;
}();
12、null和undefined的区别汇总
null==undefined =>true
null===undefined =>false
0和空字符串与null和undefined的区别:
null和undefined相比于0和空字符串占用的内存更少
null:
空对象指针->没有具体的值->一般都是我们手动设置初始值为null,后期会给其赋值;
undefined:
未定义->连东西都没有->一般都是浏览器默认的值;
用null的几种情况:
1,设定变量初始值:我们设定一个变量,后期我们要使用,那么我们设置默认值为null
2,释放堆内存:在JS内存释放中,我们想释放一个堆内存,就让其值变为null即可
3,在DOM元素获取中,不存在则结果为null:我们通过DOM中提供的属性和方法获取页面中的某一个元素标签,如果这个标签不存在,获取的结果是null,而不是undefined
4,在正则的exec/字符串的match捕获中,如果当前要捕获的字符串和正则不匹配的话,捕获到的结果为null;
用undefined的集中情况:
1,JS预解释的时候只声明未定义,默认值为undefined;
2,在一个函数中,如果没有写return,或者return后什么都没写,默认返回值为undefined;
3,函数中设置了形参,但是执行的时候如果没有传递参数值,那么形参默认值为undefined;
4,获取一个对象的属性名对应属性值的时候,如果这个属性名不存在的话属性值为undefined;
5,严格模式下,this前没有明确的执行主体,this就是undefined
6,用来检测浏览器兼容问题,不兼容的话返回undefined
13、函数数据类型
函数
函数:具备一定功能的方法;
<font color=red>创建函数:</font>
1.声明一个函数名(函数用function,变量用var)
2.开辟一个新的内存空间(有一个16进制的地址),然后把函数体中实现功能的JS代码按照"字符串"的格式存储在内存中
3.把空间地址赋值给函数名,此时函数名就可以和函数体本身关联到一起了;
function 函数名(){
函数体:实现某一功能的JS代码
}
<font color=red>函数执行:</font>
1.函数执行的时候会形成一个私有的作用域,提供一个环境供函数体中的代码执行;
2.把创建的时候存储的字符串变为真正的JS代码,在私有作用域中自上而下执行
<font color=red>注意:</font>一个函数可以被执行N次,每一次执行相互之间互不干扰(后面会学习两者之间建立的间接关系)
形参:形式参数(变量),函数的入口,形参也相当于创建了一个私有变量
实参:函数执行传递给函数的具体值就是实参
函数本身也会有一些自己的属性
-->length:0 形参的个数
-->name:"Fn" 函数名
-->prototype 类的原型,在原型上定义的方法都是当前Fn这个类实例的公
有方法
-->_proto_ 把函数当做一个普通的对象,指向Function这个类的原型
call
apply
bind
call
fn.call(context,para1,…)
<font color=red>在借用数组原型上的方法把类数组转化为数组的时候,IE低版本浏览器中,元素集合或者节点集合这些类数组是无法借用slice转换的会报错“不是Javascript对象”;</font>
Function.prototype上的call方法的实现原理
Function.prototype.call=function(context){
//先明确此方法中的this指向的是call点前面的执行主体,即调用此方法的主体
1、让此方法中的this(call点前面的执行主体)里面的this关键字指向context
2、让此方法中的this(call点前面的执行主体)执行;
//以下为第二步:让this执行,即call点前面的执行主体执行;
this()
}
function fn1(){
console.log(1);
}
function fn2(){
console.log(2);
}
fn1.call(fn2);//1
fn1.call.call.call.call(fn2);//2
把方法执行,并且让方法中的this改变为context,都是给fn传递的实参
//非严格模式下
function fn(){
console.log(this);
}
fn.call(1);//1
fn.call();//不传递参数时默认为window
fn.call(null);//传递null也为window
fn.call(undefined);//传递undefined也为window
//JS严格模式下
如果不传则默认为undefined
传的话传的是什么就修改为什么;
apply
apply的语法和call基本一致,作用原理也基本一致,唯一的区别:apply把传递给函数的实参以数组形式存放(但是也相当于在给函数一个个的传递实参值)
bind
(bind方法返回的是一个新的函数和原函数空间地址不同);
第一个参数为要改变的执行主体中的this关键字,
第一个之后的参数都是执行主体执行的时候需要传递给执行主体的参数,传递给执行主体的参数会放在执行主体自带的参数之前;
改变函数中this关键字,在IE6-8下不兼容;它和call(以及apply)改变this的原理不一样
预先让fn中的this指向opp,此时fn没有执行,只有fn执行的时候才起作用;
bind方法兼容IE6-8版
var flag='getComputedStyle' in window;
Function.prototype.myBind=function (context) {
context=context||window;
var that=this;
var outerArg=Array.prototype.slice.call(arguments,1);
//标准浏览器下
if(flag){
return this.bind.apply(this,arguments);
}
//IE6-8下
return function () {
//定义一个变量接收形参事件对象
var innerArg=Array.prototype.slice.call(arguments);
that.apply(context,outerArg.concat(innerArg));
}
}
例如:点击盒子的时候,执行fn,并让其中的this改变为opp
function fn(){
console.log(this);
}
oBox.onclick=fn.call(opp);/无法实现,还没有点击的时候,fn已经执行了,只是把fn的返回结果给了onclick
oBox.onclick=fn.bind(opp);//完美实现,点击的时候fn才执行,并预先让fn中的this变为opp;
在真实项目中,我们把实现一个具体功能的代码封装在函数中:
好处:
1:减少了冗余代码,开发效率高;
2:封装在一个函数中,页面中就基本上很难出现重复一样的代码了,减少了页面中代码的冗余度,提高了代码的重复利用率:低耦合高内聚;
我们把以上的特点称之为函数封装
(OOP面向对象编程思想,需要我们掌握的就是类的继承、封装、多态)
函数的核心原理
函数作为引用数据类型的一种,也是按照地址来操作的,私有作用域中不带var的声明都是给window设置的属性;
函数存在了多面性
1)作为普通函数(fn()):它本身就是一个普通的函数,执行的时候形成私有的作用域(闭包),形参赋值,预解释,代码执行,执行完成后栈内存销毁/不销毁
2)作为类,即构造函数(new Fn();):它有自己的实例,也有一个叫做prototype属性是自己的原型,它的实例都可以指向自己的原型
3)作为普通对象(fn.aaa):和var obj={}中的obj一样,就是一个普通的对象,它作为对象可以有一些自己的私有属性,也可以通过____proto____找到Function.prototype
- 所有的函数都可以调取Function.prototype(类也是函数)上的方法:如call、apply、bind
- 所有的对象都可以调取Object.prototype(函数也是对象)上的方法:如toString、hasOwnProperty
普通函数执行和构造函数执行的区别
构造函数执行的时候,也是先形成一个私有作用域,形参赋值,变量提升,在代码从上而下执行之前,构造函数有特殊的操作:
浏览器会在当前的作用域中默认创建一个对象数据类型的值,并且会让构造函数中的this指向创建的这个对象。
然后JS代码再执行,代码执行完成后,即使函数中没有写return,在构造函数模式中:浏览器会默认的把创建的对象返回到函数外面
<font color=red>总结:</font>
- 构造函数执行期间,既具备函数执行的一面,也同时具备自己独有的操作:在构造函数执行期间,浏览器会默认创建一个对象,这个对象就是当前这个构造函数(类)实例,函数执行完成后,浏览器会默认的把这个实例返回。所以new Fn()执行,Fn是一个类,返回的结果就是Fn这个类的一个实例。
阿里面试题执行顺序总结
new Foo.getName();
new Foo().getName();
new new Foo().getName();
执行顺序总结
:(经验之谈目前来说准确)
<font color=red>1、new 操作符只能new构造函数,否则会报错,xxx is not a constructor
2、new碰到()就会把new与()之间的当成构造函数处理,所以会先执行new与()之间的运算,之后再new。<font>
栈内存
:
- 作用域(全局/私有作用域):提供一个供JS代码执行的环境;执行JS代码的
- 基本数据类型的值是直接存放在栈内存中的
销毁:
+ 一般情况下,函数执行形成栈内存,函数执行完,浏览器会把形成的栈内存自动释放;有时候执行完成,栈内存不能被释放。
全局作用域在加载页面的时候执行,在关掉页面的时候销毁;
堆内存
:
只要遇到对象数据类型或函数数据类型,浏览器就会创建一个堆内存。存储引用数据类型值的
所有的引用数据类型,他们需要存储的内容都放在堆内存中(相当于一个仓库,目的是存储信息的)
- 对象会把键值对储存起来
- 函数会把JS代码当做字符串储存起来
- 销毁:
- var o={}; 当前对象对应的堆内存被变量o占用着呢,堆内存是无法销毁的
- o=null; null空对象指针(不指向任何的堆内存),此时上一次的堆内存就没有被占用了,谷歌浏览器会在空闲时间把没有被占用的堆内存自动释放(销毁/回收)
定义变量的时候带var和不带var的区别?
在全局作用域中,带不带var都一样,都相当于声明了一个全局变量,给全局对象设置了一个新的属性名,但是不带var的不能提前声明,所以在赋值前不能提前调用;
在局部作用域中,带var的话声明的都是私有变量,不带var的话声明的都是全局变量,也相当于给window设置了一个属性,也不能提前调用。
普通函数执行步骤:形成一个私有作用域
1.形参赋值
2.变量提升(形参赋值后,如果有函数声明且与形参名字相同,则覆盖形参的值,var声明相当于重复声明,不会覆盖形参的值)
3.代码从上到下执行
4.栈内存销毁、不销毁;
变量提升
在
当前作用域
中,JS代码从上而下执行之前,浏览器会把所有带var和function关键字的进行提前声明
,函数声明和定义同时完成
;(只对当前作用域下的变量或者函数起作用)
预解释:
1,发现重复的,不重复声明,只定义,重复定义只会替换
之前的值;(函数声明加定义一起完成)
2,预解释不管条件;(在最新的浏览器(IE11及以上),不管条件成立与否都会提前声明,不会提前定义,然后再看条件是否成立,如果成立则看有没有函数,如果有函数,则先定义,如果没有,JS代码从上到下执行成立的代码。如果不成立,则走不成立的代码)IE10及以下(不管条件是否成立函数都会进行声明+定义);
3,只对等号左边的进行变量提升,右边是值,不会提前声明什么的;
4,预解释发生在同一个脚本块中。
5,return 后面跟着的是值,不会进行预解释,但是return下面的代码要进行预解释;
所有声明变量或声明函数都会被提升到当前函数的顶部。
作用域链
在私有作用域中
声明的变量
和函数的形参
都是私有的变量;
在私有作用域中,代码执行的时候遇到一个变量,首先看它是否为私有变量:
[是私有变量]
- 则和外面没有任何关系,以后在这个作用域中操作的当前变量都按照私有的处理;
[不是私有变量]
- 则往当前作用域的上级作用域进行查找,如果上级作用域中有,我们操作的都是上级作用域的中的变量(假如我们在当前作用域把值改了,相当于把上级作用域中的这个值给修改了)。如果上级作用域也没有,则继续往上查找,一直照到window为止,这就是作用域链;
- 如果找到window下也没有,分两种情况:**
- 我们是获取值:console.log(total);->报错,下面代码不再执行
- 我们是设置值:total=100;->相当于给window增加了一个属性名叫total,属性值为100;
如何查找函数的上级作用域?
<font color=red>看当前函数是在哪个作用域下定义的,那么它的上级作用域就是谁,和函数在哪执行没有任何关系;<font>
只有函数执行的时候会产生私有作用域;
console.log(x,y);//undefined undefined
var x=10,
y=20;
function fn() {
console.log(x,y);//undefined 20(为什么不是100而是20,因为下一行的y=100还没有执行 哈哈哈被骗了吧)!
var x=y=100;
console.log(x,y);//100 100
}
fn();
console.log(x,y);//10 100 为什么x是10而不是100,因为闭包里的x=100外界无法拿到;
作用域是否销毁
堆内存
1.对象数据类型或者函数数据类型在定义的时候首先都会先开辟一个堆内存,堆内存有一个引用地址,如果有变量或者元素事件知道了这个地址,我们就说这个堆内存被占用了,那么就要考虑这个堆内存是否会销毁;
栈内存
全局作用域:
- 只有当页面关闭的时候全局作用域才会销毁。
私有作用域
一般来说,函数体中的代码执行完成,形成的栈内存会立即释放(自执行函数也不例外),当然也有不释放的。
- 当私有作用域中的堆内存的地址被作用域以外的东西占用了,那么当前的这个作用域就不能被销毁(下面为三种情况)
- 函数执行形成一个私有作用域,如果私有作用域中的部分内容被以外的变量占用了,当前作用域不销毁
如:函数返回了一个引用数据类型
的值,而且在外面有一个变量接受了这个返回值,此时当前作用域就不能销毁,想要销毁只需把外面的变量赋值为null,即解除占用即可;
如果返回的是一个基本数据类型的值,而且外面有一个变量接收,当前私有作用域是否会销毁?
会销毁,因为返回引用数据类型的值会在私有作用域中开辟一个堆内存,而且这个堆内存被外面的变量或元素元素事件占用着,所以这个堆内存不销毁,进而导致这个堆内存所在的私有作用域也不会销毁。而基本数据类型值是直接把值拷贝一份给外面的变量,所以返回完成后,函数中的堆内存没有被占用,就会被销毁掉,形成的私有作用域也会被销毁掉;
- 在一个私有的作用域中给DOM元素的事件绑定方法,一般情况下我们的私有作用域都不销毁
- 下述情况属于不立即销毁
函数执行返回一个函数没有被其他东西占用,但是还需要执行一次,所以暂时不销毁,当返回的函数执行完成后,浏览器会在空闲的时候把它销毁了;
<font color=red>总结:作用域是否销毁就看两步:
- 1、这个作用域是否产生了堆内存
- 2、这个作用域产生的堆内存是否被这个作用域以外的变量或事件占用了,如果被占用了,那么这个堆内存不能被销毁,进而导致这个私有作用域也不能被销毁。<font>
如果产生的堆内存被自身作用域中的变量占用了,那么这个堆内存会在被使用后销毁,所在的私有作用域也会被销毁
闭包
函数执行会形成一个私有的作用域,保护私有变量不受外部影响,从外部拿不到里面的变量,此时我们可以理解为私有作用域把私有变量保护了起来,这种保护机制称之为'闭包'。由方法运行而产生的私有作用域就叫闭包,为了让变量更安全。闭包是一种机制而不是某种形式;
闭包面试总结:
闭包就是连接私有作用域和外部作用域之间的一座桥梁,它不仅能保护私有变量不受外界干扰,还能保存一些内容,而且还能把私有作用域中的东西拿到外部使用。如果我想在外部使用闭包中的值怎么办?
1.在闭包中设置返回值,在外部声明一个变量接收。
2.把闭包中的值赋值给window的一个属性。
闭包的作用
1.保护
形成私有作用域,保护里面的私有变量不受外界的干扰,真实项目中,我们利用这种保护机制,实现团队协作开发(避免了多人同一个命名,导致代码冲突的问题)
- jQuery:常用的JS类库,提供了很多项目中常用的方法(兼容所有浏览器)
- Zepto:小型JQ,专门为移动端开发准备的
//JQ代码片段
(funciton(window)){
var jQuery=function(){
....
}
....
window.jQuery=window.$=jQuery;
}(window);
jQery()
$()
//Zepto代码片段
var Zepto=(function(){
var Zepto=funciton(){
...
};
....
return Zepto;
})();
var $=Zepto
Zepto();
$();
真实项目中,我们利用这种保护机制,实现团队协作开发(避免了多人同一个命名,导致代码冲突的问题)
//A
(funciton()){
//A写的代码
function fn(){
....
}
....
window.fn=fn;
}();
//B
(function(){
//B写的代码
funciton fn(){
...
};
//B想要调取A写的fn
window.fn();
})();
2.保存
函数执行形成一个私有作用域,函数执行完成,形成的这个栈内存一般情况都会自动释放
但是当栈内存中的内容被栈内存以外的其他东西(变量/元素的事件)占用了,那么这个栈内存就不能被释放掉,也就形成了不销毁的私有作用域(里面的私有变量也不会销毁);
应用:高级单例模式;
i++与++i的区别
:
i++,先拿原有的值和其他值运算,运算后再累加1;
++i,先累加1,再拿结果进行运算;
++i和i=i+1的区别
++i在拿到i的值的时候,会先用Number()转化一下,然后再累加1,之后再运算,而i=i+1不会;
函数的中的this问题
当前函数的执行主体,
this指向是函数执行时决定的 不是编写代码时指定
。this的几种情况:
全局中的this是window,我们都研究函数内部的this的指向问题
在JS的非严格模式下
- 1.自执行函数中的this都是window
- 2.给元素的某个事件绑定方法,当事件触发执行对应方法的时候,方法中的this一般都是当前操作的元素本身(在IE6-8下DOM2级事件用attachEvent绑定方法时,方法中的this是window,而不是当前操作元素本身)
- 3.函数执行前的主体
+ fn(); this为windows
+ obj.fn(); this为obj
- 4.ES6中箭头函数中的this继承宿主环境中的this:看方法在哪定义的,宿主环境就是谁,箭头函数中的this就是宿主环境中的this;
var obj={
fn:function(){
//=>this:obj
setTimeout(function(){
//=>this:window 不管在哪执行,定时器中的this是window
},1000);
//=>想让定时器函数中的this也是obj
setTimeout(function(){
//=>this:obj
}.bind(this),1000);
var _this=this;
setTimeout(function(){
//=>_this:obj
_this.name
='xxx';
},1000);
setTimeout(()=>{
//=>this:obj 箭头函数中的this继承宿主环境(上级作用域中)的this
},1000);
}
};
obj.fn();
在JS严格模式下(让JS更加严谨)
开启严格模式:在当前作用域的第一行加 "use strict"。开启后所有作用域下再执行的JS代码都按照严格模式处理
- 严格模式下,没有写执行主体的话,this就是undefined;
arguments实参集合(只有传递了实参才会保持映射,不传递一直都是undefined)
当我们不知道用户具体要传递几个值得时候(传递几个值都可以),此时我们无法设置形参的个数;遇到此种情况,需要使用函数内置的实参集合:arguments
1.arguments只有函数才有,是一个类数组集合
- 1、以数字作为索引(属性名),从零开始
- 2、有一个length属性,存储的是当前实参的个数 arguments.length
- 3、arguments.callee 动态的得到当前执行函数的方法名
2.不管执行函数是否传递参数,arguments天生就存在,没有传递参数时arg是个空的集合,传递了参数的arg中包含了所有传递的参数信息,
如果没有传递实参,那么形参的值在arguments中就是undefined
如果有变量名与形参冲突,那么操作的一直都是私有变量,argument中没有传递实参的形参的值还是undefined
var a=12,b=13,c=14;
~function(a,b){
//b没有传递实参所以在arguments中的值一直都是undefined
b=b||0;
arguments[0]=100;
var b=c=200;
console.log(a);
console.log(arguments[1]);//undefined
}(a);
console.log(a);
console.log(b);
console.log(c);
<font color=red>非严格模式下,如果传递的有实参,那么arguments的值永远和对应命名参数的值保持同步,如果没有传递实参,则arguments中形参对应的值就一直是undefined(来自高程3第66页,在严格模式下arguments将不与实参保持映射关系。重写arguments 的值会导致语法错误(代码将不会执行)),但是如果使用delete 删除了arguments中某一个索引对应的值,再去修改实参,那么实参和arguments将不再保持同步;<font>
(function (a) {
delete arguments[0];
a=1;
console.log(a);//1
console.log(arguments);
//[empty × 1, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// callee:ƒ (a)
// length:1
// Symbol(Symbol.iterator):ƒ values()
// __proto__:Object
})(2);
JS中函数的返回值return
函数中是有返回值的,我们如果想在外面使用函数私有的一些信息,那么就需要通过return,把这些信息返回出来供外面使用。如果不写return或者return后面什么都不写,则默认返回undefined。函数体中,return后面的代码不再执行
return后面跟着的都是值:
function sum(){
var val=0;
return val;//返回的不是val变量,而是val所存储的值
}
sum //代表函数本身
sum() //函数执行,代表的是当前函数执行后返回的结果(return后面是什么函数返回的就是什么)
JS中的匿名函数(没有名字的函数)
函数表达式—>把一个匿名函数(有名字也可以)作为值赋值给一个变量或者一个元素的某个事件)
- 声明的变量指向函数体时(即函数表达式),变量的声明会被提升(foo的声明会被提升),但是它指向的函数体只会在执行的时候才被赋值。对于 var bar = function foo(){};语句,其实就是一个有效的命名函数表达式,但有一点需要记住:命名函数表达式的标示符(即函数名称)在外部作用域是无效的,在其内部作用域是有效的。
自执行函数—>创建函数和执行函数放在一起了,创建完成后立马执行;(自执行函数什么时候执行?预解释之后,JS代码从上到下执行,碰到自执行函数时,才会创建和执行;)
以下都是自执行函数,符号只是控制语法规范
除了第一种,其他4中方式都会改变自执行函数的返回结果
- (function(){})();
- ~function(n){}(10); ~:按位非,执行按位非的结果就是返回数值的反码
- -function(n){}(10);
- +function(n){}(10);
- !function(n){}(10);
14、JS中操作DOM的属性和方法
<font color=red>操作真实的DOM结构是比较消耗性能的(尽量减少)</font>
DOM的映射机制
浏览器在渲染页面的时候,给每一个元素都设置很多内置的属性(包含样式的),当我们在JS中把堆内存中的某些东西修改了,大部分情况下,浏览器都会检测到你的修改,按照最新的修改的值重新渲染页面中的元素。
DOM的重绘和回流
重绘(repaint)针对某一个元素
当元素的样式发生改变(不修改元素位置的样式),浏览器会把当前元素重新的进行渲染(DOM性能消耗低)
触发重绘的场景:
- color的修改
- text-decoration的修改
- background的修改
- a:hover也会造成重绘
- :hover引起的颜色等不导致回流的style变动
回流(reflow)针对整个页面
当元素的位置发生改变,浏览器会把整个页面的DOM结构进行重新计算,计算出所有元素的最新位置,然后再渲染(DOM性能消耗非常大)
- 1、width/height/border/margin/padding的修改
- 2、动画,:hover等伪类引起的元素表现改动,display=none等造成页面回流
- 3、appendChild等DOM元素操作
- 4、font类style的修改
- 5、background的修改,部分background的修改只触发重绘,IE不用考虑
- 6、scroll页面,不可避免
- 7、resize页面,桌面版本进行浏览器大小的缩放。
- 8、读取元素属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE));
如何解决重绘和回流
如何避免:
尽可能在 DOM 末梢通过改变 class 来修改元素的 style 属性:尽可能的减少受影响的 DOM 元素。
避免设置多项内联样式:使用常用的 class 的方式进行设置样式,以避免设置样式时访问 DOM 的低效率。
设置动画元素 position 属性为 fixed 或者 absolute:由于当前元素从 DOM 流中独立出来,因此受影响的只有当前元素,元素 repaint。
牺牲平滑度满足性能:动画精度太强,会造成更多次的 repaint/reflow,牺牲精度,能满足性能的损耗,获取性能和平滑度的平衡。
避免使用 table 进行布局:table 的每个元素的大小以及内容的改动,都会导致整个 table 进行重新计算,造成大幅度的 repaint 或者 reflow。改用 div 则可以进行针对性的 repaint 和避免不必要的 reflow。
避免在 CSS 中使用运算式:学习 CSS 的时候就知道,这个应该避免,不应该加深到这一层再去了解,因为这个的后果确实非常严重,一旦存在动画性的 repaint/reflow,那么每一帧动画都会进行计算,性能消耗不容小觑。
获取节点
<font color=red>document.getElementById()</font>
在文档中通过元素的ID名来获取一个元素,只能通过document对象来调用,document属于Document类的一个实例,只有在Document类的原型上才有getElementById这个方法。
我们把document称之为上下文(context):上文和下文,也就是获取元素时候限制的那个范围。
获取的结果是一个对象数据类型的值,在JS中使用DOM提供的方法,获取的元素都是对象,所以我们把获取的结果称之为 ‘元素对象’。
1.如果页面中的ID重复了,我们获取的结果是ID对应的第一个元素对象
2.在IE7及以下会把表单元素中的name属性值当做ID来使用;而且会忽略ID的大小写(项目中尽量不要让表单的name和其他元素的id相同)
3.我们如果把JS放在结构下面,我们可以直接使用ID值来获取这个元素(不需要通过getElementById来获取),而这种方式会把页面中所有ID为同一个的都获取到(只有一个的话获取的是元素对象,多个的话获取的是类数组集合)=>不推荐
<font color=red>注意:不要让表单元素的name属性值和其他元素的id重复、不要用id的大小写来区分不同的元素</font>
不通过直接操作ID值的方式如何获取多个相同ID的元素?
var allList=document.getElementsByTagName('*');
var ary=[];
for(var i=0;i<allList.length;i++){
allList[i].id===xxx?ary.push(allList[i]):null
}
return ary;
<font color=red>context.getElementsByTagName():</font>
在指定上下文中通过元素的标签名来获取子子孙孙元素中的一组元素,获取到的元素是类数组集合,通过索引来调用某一个
1、以数字作为属性名,每一个属性存储的都是获取到的每一个li,JS中我们把数字属性名叫做“索引”(索引是逐级递增的)
2、有一个length属性存储的是当前集合中LI的个数
具备以上的两个特点特别像数组,但是,它不是数组,所以我们把他称之为“类数组”
<font color=red>document.getElementsByName():
</font>在指定上下文中通过元素的name属性来获取一组元素(类数组:节点集合 NodeList)
在IE浏览器下只对表单元素起作用
这个方法应用于获取具有同样name 的表单元素
<font color=red>context.getElementsByClassName():
</font>通过元素的样式类名来获取(class值)一组元素,获取的也是一个类数组集合,通过索引来调用某一个;
getElementsByClassName()是项目中最常用的一种方法,但是这个方法在IE6-8下不兼容
<font color=red>document.documentElement:</font>获取HTML元素
<font color=red>document.body:</font>获取body元素
在移动端获取元素常用的方法(IE6-8下不兼容)
documetn.querySelector():
获取一个
document.querySelectorAll():
获取多个 类数组集合(获取到的元素集合或节点集合不存在DOM映射,因为获取到的集合不是标准的NodeList,而是属于StaticNodeList(静态集合))
document.querySelector("#tab");ID选择器
document.querySelectorAll("#tab li ");//层级选择器
document.querySelectorAll("input[type='radio']");//过虑选择器
className:
通过这个属性可以获取或者设置当前元素对象的样式类
innerHTML:
通过这个属性可以获取或者设置元素里面的内容
innerText:
通过这个属性可以设置或者获取元素里面的文本内容
火狐中不支持innerText属性,我们用textContent代替innerText
<font color=red>innerHTML与innerText的区别:</font>
- innerHTML可以把增加内容中的HTML标签进行识别,innerText只能设置或者获取文本内容,不能识别HTML标签,HTML标签会被当作文本处理
style属性:
通过这个属性我们可以获取或者设置元素的样式(只能是元素的行内样式,获取或者设置都是,写在内嵌或者外链中的css样式无法获取到)
window.getComputStyle(元素,伪类)[属性值] || 元素.currentStyle[属性值]:
获取当前元素经过浏览器计算的属性值,不管是写在哪的;
获取关系的属性
childNodes
:获取所有的子节点
获取当前元素的所有子节点(节点集合:类数组)
注:不仅是元素子节点,文本、注释等都会包含在内;子节点说明只是在儿子辈分中查找;
childredn
:获取所有的元素子节点
获取所有的元素子节点(元素集合)
在IE6-8下获取的结果和标准浏览器中有区别:会把注释节点当作元素节点获取到
parentNode
:获取元素的父亲节点(元素对象)
previousSibling
:获取哥哥节点(包括文本或注释)
nextSibling
:获取弟弟节点(包括文本或注释)
IE6-8下不兼容
previousElementSibling
:获取上一个元素节点
nextElementSibling
:获取下一个元素节点
firstChild
:获取所有子节点中的第一个
lastChild
:获取所有子节点的最后一个
firstElementChild/lastElementChild IE6-8下不兼容
节点类型
节点类型 nodeType nodeName nodeValue 元素节点 1 大写的标签名 null 属性节点 2 大写的属性名 属性值 文本节点 3 #text 文字内容 注释节点 8 #comment 注释内容 document 9 #document null 在标准浏览器下,我们把空格和回车都当成文本节点处理
ele.tagName:获取当前元素的标签名(获取的一般都是大写),tagName只有元素节点才有;
节点操作
创建节点
document.createElement(元素标签):动态创建一个元素节点
document.createAttribute(元素属性):动态创建一个属性节点
document.createTextNode(文本内容):动态创建一个文本节点
插入节点
appendChild():把元素添加到指定容器的末尾位置
容器.appendChild(元素)
insertBefore(a,b):把新的元素a,添加到老的元素b之前
容器.insertBefore(a,b)
替换节点
replaceChild(a,b):用a元素来替换容器中的b元素
容器.replcaeChild(a,b);
复制节点
cloneNode(true/false):克隆元素节点,true表示包括所有子节点,默认为false
要克隆的元素.cloneNode(),无法克隆元素绑定的事件
删除节点
removerChild():从容器中删除指定元素
容器.removeChild(元素)
属性操作
获取属性
getAttribute(元素属性名):获取元素节点中指定属性的属性值
元素节点.getAttribute(元素属性名)
设置属性
setAttribute(元素属性名):设置或者改变元素节点的属性值(一般都是操作自定义属性)
元素节点.setAttribute(属性名,属性值),可以设置自定义的属性,会修改html结构,直接在html中体现出来。用setAttribute设置的只能用getAttribute来获取,只能用removeAttribute来删除
注意:在IE6-8下无法修改和设置class属性
删除属性
removeAttribute(元素属性名):删除元素节点的属性
元素节点.removeAttribute(属性名)
设置或者获取自定义属性有xxx.属性名=属性值和 xxx.setAttribute(元素属性名)两种方式,有以下区别
:
1,xxx.属性名 结果体现在对象的属性上。与HTML结构无关。
1,xxx.setAttribute(属性名) 结果体现在DOM结构上,即在HTML结构中可以看到;
如果使用DOM的内置属性操作元素,那么元素就会被当作特殊对象,和HTML结构产生映射关系(即结果会呈现在HTML结构上)