javascript杂谈

一、浏览器的渲染原理

浏览器渲染过程如图所示
javascript杂谈_第1张图片


1、从上面这个图中,我们可以看到那么几个事:

1)浏览器会解析三个东西:

  • 一个是HTML/XHTML,事实上,Webkit有三个C++的类对应这三类文档。解析这三种文件会产生一个DOM Tree。
  • CSS,解析CSS会产生CSS规则树。
  • Javascript,脚本,主要是通过DOM API和CSSOM API来操作DOM Tree和CSS Rule Tree.

2)解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。注意:

  • Rendering Tree 渲染树并不等同于DOM树,因为一些像Header或display:none的东西就没必要放在渲染树中了。
  • CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个Element。也就是DOM结点。也就是所谓的Frame。
  • 然后,计算每个Frame(也就是每个Element)的位置,这又叫layout和reflow过程。

3)最后通过调用操作系统Native GUI的API绘制。


2、渲染


渲染的流程基本上如下:

  1. 计算CSS样式
  2. 构建Render Tree
  3. Layout – 定位坐标和大小,是否换行,各种position, overflow, z-index属性 ……
  4. 正式开画

这里重要要说两个概念,一个是Reflow,另一个是Repaint。这两个不是一回事。

  • Repaint——屏幕的一部分要重画,比如某个CSS的背景色变了。但是元素的几何尺寸没有变。
  • Reflow——意味着元件的几何尺寸变了,我们需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。这就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式布局,所以,如果某元件的几何尺寸发生了变化,需要重新布局,也就叫reflow)reflow 会从<html>这个root frame开始递归往下,依次计算所有的结点几何尺寸和位置,在reflow过程中,可能会增加一些frame,比如一个文本字符串必需被包装起来。
Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的

所以,下面这些动作有很大可能会是成本比较高的。

  • 当你增加、删除、修改DOM结点时,会导致Reflow或Repaint
  • 当你移动DOM的位置,或是搞个动画的时候。
  • 当你修改CSS样式的时候。
  • 当你Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
  • 当你修改网页的默认字体时。

注:display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。

基本上来说,reflow有如下的几个原因:

  • Initial。网页初始化的时候。
  • Incremental。一些Javascript在操作DOM Tree时。
  • Resize。其些元件的尺寸变了。
  • StyleChange。如果CSS的属性发生变化了。

3、页面加载顺序

1)、IE下载的顺序是从上到下,渲染的顺序也是从上到下,下载和渲染是同时进行的。
2)、在渲染到页面的某一部分时,其上面的所有部分都已经下载完成(并不是说所有相关联的元素都已经下载完)。
3)、如果遇到语义解释性的标签嵌入文件(JS脚本,CSS 下载过程会启用单独连接进行下载,并且在下载后进行解析,解析过程中,停止页面所有往下元素的下载。
4)、样式表在下载完成后,将和以前下载的所有样式表一起进行解析,解析完成后,将对此前所有元素(含以前已经渲染的)重新进行渲染。
5)、JS、CSS中如有重定义,后定义函数将覆盖前定义函数。

如果你的网页比较大,希望部分内容先显示出来,粘住浏览者,那么你可以按照上面的规则合理的布局你的网页,达到预期的目的。

 JS的加载
不能并行下载和解析(阻塞下载)
当 引用了JS的时候,浏览器发送1个jsrequest就会一直等待该request的返回。因为浏览器需要1个稳定的DOM树结构,而JS中很有可能有代 码直接改变了DOM树结构,比如使用 document.write 或 appendChild,甚至是直接使用的location.href进行跳转,浏览器为了防止出现JS修改DOM树,需要重新构建DOM树的情况,所以 就会阻塞其他的下载和呈现.

二、ajax原理解析

function GetXmlHttpObject() {
	var xmlHttp = null;
	try {
		// Firefox, Opera 8.0+, Safari
		xmlHttp = new XMLHttpRequest();
	} catch (e) {
		// Internet Explorer
		try {
			xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
		} catch (e) {
			xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
		}
	}
	return xmlHttp;
}

//关键点1
var xmlHttp = GetXmlHttpObject();


//关键点2:success,failue处理
xmlHttp.onreadystatechange = function () {
	if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
		var data = xmlHttp.responseText;
		document.getElementById("myDiv").innerHTML = data;
	}
};
xmlHttp.open("GET", "url", true);
console.log(xmlHttp);
xmlHttp.send();
var result = xmlHttp.responseText;
console.log(result);


1、从以上代码可知javascript原生ajax主要有以下几个关键点:

1)xmlHttpRequest对象创建谷歌、火狐、ie

2)回调函数处理

3)回调数据处理(原生ajax需要自己处理json)


2、多一句嘴

我这里是从本地一个js文件中直接调用后台服务器的,所以需要处理浏览器跨域问题,如果你跟我一样也喜欢用chrome,则可在谷歌快捷方式中添加如下一行参数:

--args --disable-web-security



3、要不我们也来玩玩javascript ajax封装吧, 啦啦啦

var Ajax = function () {
	function request(opt) {
		var xhr = GetXmlHttpObject();
		xhr.onreadystatechange = function () {
			myOnreadystatechange(xhr, opt.success, opt.error);

		};

		xhr.open(opt.method, opt.url, opt.async);
		xhr.send();
		return xhr;
	}
	return {
		request: request
	};
}();


function myOnreadystatechange(xhr, success, error) {
	if (xhr.readyState === 4 && xhr.status === 200) {
		success(xhr);
	} else {
		error(xhr);
	}

}

console.log(Ajax);
var opt = {
	url: "http://localhost:9999/gotian-ws/api/user/sayHello/fengshu",
	method: "GET",
	async: true,

	success: function (xhr) {
		var data = xmlHttp.responseText;
		console.log(data);
	},

	error: function (xhr) {
		var data = xmlHttp.responseText;
		console.log(data);
	}
	
};
Ajax.request(opt);


三、回调函数

封装自定义ajax其实是为讲回调埋下一个伏笔,而回调其实就是一个”伏笔“,你能明白我这句话的意思吗?

1、什么是回调函数

什么是Javascript 回调函数?
函数和其他数据一样可以被赋值,删除,拷贝等,所以也可以把函数作为参数传入到另一个函数中。
这个函数就是所谓的回调函数。

2、再讲一遍

函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。


3、例子

function add(a, b, callback) {
	return callback(a, b);
}

var result = add(2, 4, function callbackAdd(a, b) {
	return a + b;
});


4、再明白点

通常大家都是上面那样写回调函数,但是这样对于不常或者没有接触过回调的开发人员会比较难懂,那么稍作改动一下,应该就懂了  吧?

function add(a, b, callback) {
	var result = callback(a, b);
	return result;
}

var result = add(2, 4, function callbackAdd(a, b) {
	return a + b;
});

5、这孙子是如何知道a=2,b=4的(回调函数的参数问题)

这里我们要想明白一件事情,add是一个方法,callbackAdd也是一个方法,callBackAdd除了是一个方法还是add方法的一个参数,那么既然是参数这里casllBackAdd方法总的参数a,b实际上需要add方法中来传的,再没传之前他只是个形参,当然传完后他还是一个形参。

有点绕,我在重写一下上面这个例子。

function add(a, b, callback) {
	var result = callback(a, b);
	return result;
}

var result = add(2, 4, function callbackAdd(形参a, 形参b) {
	return 形参a + 形参b;
});


6、回调总结

1)如果想明白了回调函数就是一个参数,回调函数如何传参这两个问题你也就基本明白了什么是回调

2)用回调其实就是你为做某一件事情埋下伏笔,就是说你事先计划好想好了当系统执行完一些事情后你再开始执行你自己自定义的一些事情计划。

四、javascript闭包

自定义ajax除了用到回调函数外还运用了自执行函数和javascript 闭包,自执行函数这里暂且不谈,我们来聊聊javascript闭包那些事,这是javascript中比较高大上的一个功能。

1、初识闭包

闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。在下面的函数中print函数就是一个闭包

function randomAlert()
{
	var x=Math.random();
	 function  print()
	{
console.log("产生的随机数",x);
	}
	return print;
}
var a=randomAlert();
var b=randomAlert();
a();
b();



2、闭包的作用

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,
另一个就是让这些变量的值始终保持在内存中。

function f1() {    
	var n = 999;    
	nAdd = function () {
		n += 1;
	}; // nAdd全局变量 
	    

	function f2() {
		console.log(n);    
	}    
	return f2;  
}  
var result = f1();  
result(); // 999
  
nAdd();  
result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,
而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

五、javascript作用域

1、javascript有作用域吗?

在编程语言中,作用域控制着变量的可见性及生命周期。对程序员来说这是一个重要的帮助,因为它减少了命名冲突,并且提供了自动内存管理。

function fengshu() {
    // 函数体内存在三个局部变量 i j k
    var i = 0;
    if (i === 0) {
        var j = 10;
        for (var k = 0; k < 3; k++) {
            console.log(k); // 0 1 2
        }
        console.log(k); // 3
    }
    console.log(j); //10
}

大多数使用c语言语法的语言都拥有块级作用域,在一个代码块中(括在一对花括号中的语句集)定义的所有变量在代码块的外部是不可见的定义在代码块中的变量在代码块执行结束后被释放掉,这是件好事。

糟糕的是,尽管代码块的语法是否表现出它支持块级作用域,但实际上javascript并不支持,这个混淆之处可能成为错误之源。

javascript确实有函数作用域,那意味着定义在函数中的参数和变量在函数外部是不可见的,而且在一个函数中的任何位置定义的变量在该函数的任何地方都可见。

很多现代语言都推荐尽可能迟地声明变量。而且在javascript上的话却会成为糟糕的建议,因为它缺少块级作用域。所以最好的办法是在函数体的顶部声明函数中可能用到的所有变量。

2、结论

所以,从javascript 作用域得出的结果是javascript有函数作用域,但并没有所谓的块级作用域。



六、javascript、jquery插件的正确使用方法-----ligerui为例

1、使用一种前端插件的一般步骤:

  • api文档
  • demo
  • 开始开发

2、一般步骤后的非同凡响的步骤-源码阅读

通过阅读源码确认一下几件事情:

  • 插件的顶级对象
  • 插件的整体框架原理
  • 插件是如何进行模块划分的
  • 阅读一些比较常用模块的具体实现方法,明白功能的实现原理

3、去打开ligerui源码阅读吧


后记---论javascript修炼之道

这里分享下一些javascript的学习经验。 

1、pdf等电子书

学习javascript个人首选pdf,每天的上下班时间是最好的阅读时光。

在这里推荐一些比较好的去处。

前端学习的另一种途径是逛IT乱弹,看IT杂志。网上学习胜在资源比较丰富,但网站也分三六九等,三等网站又分三六九等开发人员,几乎每个网站都充斥着大学生和入门级开发人员,十分乱,你需要有一双火眼金睛来区分学习资源的好坏 。

  • 陈皓个人博客 http://coolshell.cn/
  • github 大量优秀开源资源   https://github.com/  
  • infoq   高级程序员喜欢逛的地方 http://www.infoq.com/
  • codeWars  http://www.codewars.com/
  • stackoverflow  程序员的谷歌 http://stackoverflow.com/
  • oschina  你可以在这里学技术博客 

2、论坛杂志 

阮一峰个人博客  网络大牛  http://www.ruanyifeng.com/blog/

3、实践是检验真理的唯一标准

javascript是史上最混乱的一种语言,乱七八糟的dom编程,方法是对象,方法可以返回一个方法,方法可以不经同意直接被覆盖掉,一个方法在不同的浏览器还可能效果不同,想想都烦。基于以上情况开发人员应该自己多去实践多学习





你可能感兴趣的:(JavaScript,Ajax,回调,ligerUI,浏览器跨域)