javascript如何渲染到页面
分为三步:
- 加载:根据请求的URL进行域名解析,向服务器发起请求,接收文件(HTML、JS、CSS、图像等)
- 解析:对加载到的资源(HTML、JS、CSS)进行语法解析,建立相应的内部数据结构(比如HTML的DOM树,JS的对象属性表,CSS的样式规则)
- 渲染:构建渲染树,对各个元素进行位置计算,样式和计算等等,然后构建渲染树对页面进行渲染(画元素)
这几个过程不是完全孤立的,会有交叉,比如HTML加载后就会进行解析,然后拉取HTML中指定的CSS,JS等。
JS一般放在文件的什么位置?
如果使用window.onload函数,将js代码放在其中,则放在哪里都是一样的,因为都是在body加载完再执行的
如果不使用window.onload函数,放在head中的话,代码不会被执行,这是因为html执行顺序,确切的说是js的执行顺序。
在HTML body部分中的JavaScripts会在页面加载的时候被执行。
在HTML head部分中的JavaScripts会在被调用的时候才执行。
head 部分中的脚本:
需调用才执行的脚本或事件触发执行的脚本放在HTML的head部分中。当你把脚本放在head部分中时,可以保证脚本在任何调用之前被加载。
body 部分中的脚本:
当页面被加载时执行的脚本放在HTML的body部分。放在body部分的脚本通常被用来生成页面的内容。
JS是单线程的
单线程意味着,同一时间只能做一件事
原因:假定有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这是不知应该以哪个线程为准。
为了利用多核CPU的计算能力,HTML5T提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,并没有改变单线程的本质。
因为IO设备很慢,很多时候CPU是闲着的,不得不等结果出来,往下执行。所以这是主线程可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务,等到IO设备返回了结果,再把挂起的任务执行下去。
因此任务分为两种:
-
同步任务:(sync)
在主线程上排队执行的任务,只有当前一个任务执行完毕,才能执行后一个任务; -
异步任务:(async)
不进入主线程,进入”任务队列“的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
同步执行可被视为没有异步任务的异步执行
异步执行的运行机制如下:
(1)所有同步任务都在主线程上执行,形成一个执行线
(2)主线程之外,还存在一个“任务队列”。只要异步任务有了运行结果,就在“任务队列”之中放置一个事件;
(3)一旦执行线中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。那些对应的异步任务,结束等待状态,进入执行线,开始执行。
(4)主线程不断重复上面的第三步。
主线程从“任务队列”中读取事件,这个过程是循环不断地,所以整个的这种运行机制又称为Event Loop(事件循环)
只有主线程空了才会去读取“任务队列”,这就是JavaScript的运行机制。
function a(){
console.log("执行a参数");
setTimeout(function(){
console.log("执行a参数的延迟函数")
},1000);
};
function b(){
console.log("执行函数b");
};
a();
b();
//输出结果
执行a参数
执行b参数
执行a参数的延迟函数
解析:以上代码会先执行函数a,而且不会等到a中的延迟函数执行完才执行函数b,在延迟函数被触发的过程中就执行了函数b,当JS引擎的event队列空闲时才会去执行队列里面等待的setTimeout回调函数,这是一个异步请求的例子。
“任务队列”是一个事件的队列,IO设备完成一项任务,就在“任务队列”中添加一个事件,表示相关的异步任务可以进入“执行线"了。主程序读取”任务队列“,就是读取里面有哪些事件。
”任务队列“中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(鼠标点击、键盘输入),只要指定回调函数,这些事件就会进入"任务队列",等待主线程读取。
何为回调函数?(callback)
==回调函数是作为参数传递给另一个函数并在其父函数完成后执行的函数。==回调用于获取异步任务的结果,只是一个形参名字而已
在异步执行的模式下,每一个异步的任务都有其自己一个或者多个回调函数,这样当前在执行的异步任务执行完之后,不会马上执行事件队列中的下一个任务,而是执行它的回调函数,而下一项任务也不会等当前这个回调函数执行完,因为他也不能确定当前的回调函数执行完毕,只要引他被触发就会执行。
//定义主函数,回调函数作为参数
function A(callback){
callback();
console.log("我是主函数");
}
//定义回调函数
function B(){
setTimeout("console.log('我是回调函数')",3000)
//模仿耗时操作
}
//调用主函数,将函数B传进去
A(B);
//输出结果
我是主函数
我是回调函数
解析:定义主函数的时候,我们先让代码去执行callback()回调函数,但输出结果确实后输出回调函数的内容。
说明了主函数不用等待回调函数执行完,可以接着执行自己的代码。所以一般回调函数都用在耗时操作上面;
变量的闭包
- 全局变量
var n=999;
function f1(){
alert(n);
}
f1(); // 999
- 局部变量
函数内部变量
函数内部声明变量的时候,一定要用var命令,如果不用,实际上声明了一个全局变量。
function f1(){
var n=999;
}
alert(n); // error
如何从外部读取局部变量?
function f1(){
n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
**解析:**在上述代码中,函数f2就被包括在函数f1内部,这时f1内部所有的局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是javaScript语句中特有的“链式作用域”
子对象会一级一级的想上寻找所有的父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之,则不成立。
何为闭包?
上面代码中的f2即为闭包。
本质上闭包就是将函数内部和外部连结起来的一座桥梁。
当内部函数在定义它的作用域的外部被引用时,就创建了该函数的闭包,如果函数内部引用了位于外部函数的变量,当外部函数调用完毕后,这些变量在内存中不会被释放,因为闭包需要他们。
function a(){
var i=0;
function b(){
alert(++i);
}
return b;
}
var c=a();
c();
//代码特点:
1.函数b嵌套在函数a的内部
2.函数a返回函数b
函数a外的变量c引用了函数a内的函数b,创建了b包
闭包的作用
JavaScript种的垃圾回收机制:
在JS种,如果一个对象不再被利用,这个对象就会被GC回收。如果两个对象相互引用,而不被第三者所引用,那么这两个相互引用的对象也会被回收。
闭包的作用就是在a执行完并返回后,闭包使得JS中的垃圾回收机制GC不会收回a所占用的资源,因为函数a被b占用,b又被a外的c引用,a执行后不会被回收。
- 可以读取函数内部的变量
- 让这些变量的值始终保持在内存中。
function f1(){
var n=999;
Add=function(){
n+=1;
}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result();//999
Add();
result();//1000
解析:在这段代码中,result实际上就是闭包f2函数,一共运行了两次,第一次的值为999,第二次的值为1000.说明,f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。变量n相当于f2的全局变量。Add是一个全局变量,值为匿名函数,而这个函数本身也是一个闭包,可以在函数外部对函数内部的局部变量进行操作。
使用闭包的注意点
- 闭包使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会再成网页性能的问题,在IE中可能导致内存泄漏。解决方法是,在退出函数之前,将不适用的局部变量全部删除。
- 闭包会在父元素函数外部,改变父元素内部变量的值,不要随便改变内部变量的值
经典闭包面试题
//例一
function fun(n,o){
console.log(o);
return{
fun:function(m){
console.log(m,n);
return fun(m,n);
}
};
}
var a=fun(0);
a.fun(1);//undefined 1 0 0
a.fun(2);//undefined 2 0 0
a.fun(3);//undefined 3 0 0
var b=fun(0).fun(1).fun(2).fun(3);
//undefined 1 0 0
//2 1 1
//3 2 2
var c=fun(0).fun(1);//undefined 1 0 0
c.fun(2);//2 1 1
c.fun(3);//3 1 1
//例二
var add=(function(){
Var counter=0;
return function(){
return counter+=1;
}
})();
//console.log(add());//1
add();//表示已经执行了一次
console.log(add());//2 执行第二次
构造函数:
首先,它是函数,并且任何的函数都可以作为构造函数存在,它的本质是初始化对象。构造函数都是和new 关键词一起使用的。 new就是在创建对象,从无到有,构造函数就是在为初始化的对象添加属性和方法( 成员 )
构造函数是一种特殊的函数,它具备了创建对象和初始化对象的功能,这是由于这种构造对象的特性,才把这种类型的特殊函数称作构造函数;
使用new运算符创建对象的格式如下:
//定义函数
function student(name,chinese,math,english){
//定义属性
this.name=name;
this.chinese=chinese;
this.math=math;
this.english=english;
}
var st=new student("丁磊",90,90,100);
toString方法的重写
当我们直接在页面中打印一个对象时,实际是输出对象的toString()方法的返回值,可以自行更改toString()方法的返回值。
function f1(n){
var all=n;
var temp=function (m){
all+=m;
return temp;
};
//重写toString
temp.toString=function(){
return all;//改变函数返回值
}
return temp;
}
f1(1)(2)(3);// f 6
函数的递归
自身反复执行自身
var count=0;
function (){
count++;
console.log(count);
if(count>=10){
return;//返回主函数
}
method();
}
method();
//例:阶乘
function (){
if(n=0||n=1){
return 1;
}
return n*fun(n-1);
}
事件系统
- 事件对象:用于存储事件的状态
- 事件源对象:当前事件在操作的对像,如元素节点,文档对象,window对象,XMLHttpRequest对象等。
- 事件监听器:当一个事件源生成一个事件对象时,它会调用相应的回调函数进行操作。在IE中,事件对象恒为全局属性window.event的分身。
事件对象
Event 对象代表事件的状态,比如事件在其中发生的元素,键盘按键的状态、鼠标的位置,鼠标按钮的状态。
何时产生Event对象?
- 当用户单击某个元素的时候,我们给这个元素注册的事件就会触发,该事件的本质就是一个函数,而该函数的形参接收一个event对象
- 事件通常与函数结合使用,函数不会在事件发生前被执行
冒泡
当子元素事件触发,事件会沿着包含关系,往上级传递,每一级都可以感知到事件。
捕获:
时间捕获是和事件冒泡是相反的,也就是说,当用户触发了一个事件,这个事件是从DOM数的最上层开始一直到捕获事件源
dom对象.addEventListener(eventType,fn,false)//事件类型 回调函数 事件机制
//IE没有实现事件捕获
属性 | 描述 |
---|---|
currentTarget | 当前捕获节点的引用 |
target | 产生事件的节点引用 |
pageX | 事件发生点当前文档横坐标 |
pageY | 事件发生点当前文档纵坐标 |
type | 当前事件类型 |
which | 针对键盘和鼠标事件,这个属性能确定你到底按的是哪个键 |
relatedTarget | 返回与事件的目标节点相关的节点 |