一、闭包:
1、函数嵌套函数、内部函数可以引用外部函数的参数和变量。
参数和变量不会被垃圾回收机制所收回
function aaa(a){
var b = 5;
function bbb(){
alert(a); //内部函数引用外部函数的参数
alert(b);//内部函数引用外部函数的变量
}
}
2、好处:
1)希望一个变量长期驻扎在内存当中。
2)避免全局变量的污染
3)私有成员的存在
function aaa(){
var a = 1; //局部,避免在函数外面是全局,影响其他变量
return function (){
a++;
alert(a);
}
}
var b = aaa();
b(); //2
b(); //3
```
function fn1() {
var a = 1;
function fn2() {
//我们是可以访问到fn1中定义a的值的
//alert(a);
alert(a++);
}
fn2();
}
fn1(); // 1
fn1();// 1 //当一个函数被执行的时候,和函数有关的一些变量会被申明(出现在内存中),当这个函数执行完成以后,如果函数中申明的变量没有再被其他地方所调用,则和这个函数有关的数据自动会被销毁
function fn1() {
var a = 1;
function fn2() {
alert(a++); // alert(a); a=a+1;
//alert(++a) // a=a+1; alert(a);
}
return fn2; //返回出去的是一个引用类型的值
}
var f = fn1();// f 和 fn2 指向的同一内存地址,所以fn1中的变量和函数不会被销毁
f(); // 1
f(); // 2
f = 1; // 断开,f 与 fn2 不指向同一内存,fn1销毁其中的变量和函数
f = fn1(); // f 重新和fn2建立关系
f(); // 1
3、用法:
1)代码模块化:
函数声明 转成 函数表达式:(代码模块化,减少全局变量的污染)
第一种用法:函数自执行:
(function (){
alert(1);
})();
第二种用法:return写法:
var aaa = (function(){
var a = 1;
return function(){
a++;
alert(a);
}
})();
aaa(); //2
aaa(); //3
私有成员的存在:
var aaa = (function(){
var a = 1;
function bbb(){
a++;
alert(a);
}
function ccc(){
a++;
alert(a);
}
return {
b:bbb,
c:ccc
}
})();
aaa.b(); //2
aaa.c(); //3
在外面调用不到a,bbb,ccc
2)在循环中直接找到对应元素的索引:
var li = document.getElementsByTagName(‘li‘);
for(var i = 0;i<li.length;i++){
li[i].onclick = function(){
alert(i); // 3,点击哪一个li都是3,当点击的时候for循环已经结束了为3
}
}
闭包写法
var li = document.getElementsByTagName(‘li‘);
for(var i = 0;i<li.length;i++){
(function(){
li[i].onclick = function(){
alert(i); // 点击第一个弹出1,第二个弹出2,第三个弹出3
}
})(i);// i 驻扎在内存中。
}
return写法
for(var i = 0;i<li.length;i++){
li[i].onclick = (function(){
return function(){
alert(i);
}
})(i);// i 驻扎在内存中。
}
````
模拟选项卡:
for (var i=0; i<aInput.length; i++) {
//show(i);
(function(i) { //这里面的包括形式参数在内的所有 i 都和外面的i无关,仅仅是个形参,可以换成任何字母
aInput[i].onclick = function() {
for (var j=0; j<aInput.length; j++) {
aInput[j].style.background = ‘‘;
aP[j].style.display = ‘none‘;
}
aInput[i].style.background = ‘yellow‘;
aP[i].style.display = ‘block‘;
}
})(i);//这个i代表for循环中的i
}
以上写法就相当于,定义一个带参函数show(a),然后调用,并传参:show(i);
4、IE下会引发内存泄漏,内存一直增加占用cpu。
事件中的函数中引用外部对象时:
window.onload = function(){
var div = document.getElementById(‘div‘);
div.onclick = function(){
alert(div.id); // div 是onclock函数外部的div,引发内存泄漏
}
//解决方法1:
window.onunload = function(){
div.onclick = null;//内存释放
}
}
window.onload = function(){
var div = document.getElementById(‘div‘);
//解决方法2代码:
var id = div.id;
div.onclick = function(){
alert(id); // div 是o‘clock函数外部的div,引发内存泄漏
}
//解决方法2代码:
div = null;//让对象为空
}
二、函数声明与函数表达式:
函数声明: function 函数名(){}
函数表达式: var a = function 函数名(可写可不写)() { }
写函数名:命名函数表达式
不写: 匿名函数表达式
function aaa(){} //函数声明
前面有 =是表达式:
var a = function aaa(){} //命名函数表达式
var a = function () {} // 命名函数表达式
括号中的都是表达式:
(function aaa(){}) //表达式
位运算符都是表达式:
~function aaa(){}
+function aaa(){}
-function aaa(){}
区别:
1.函数表达式可以直接后面加括号执行。而函数声明是不可以的。
2.函数声明可以被提前解析出来的。js的预解析。
错误:function aaa(){}();
正确:var a = function aaa(){}(); //直接执行
~function aaa(){}();
(function aaa(){})();
兼容性:
函数表达式写法不要在外部调用函数名
var a = function aaa(){
alert(2);
}
a(); 所有浏览器都兼容
//aaa();//外面找不到,不推荐使用
函数表达式:
alert(fn1); //undefined
//申明一个变量fn1,赋值了一个函数
var fn1 = function() {
alert(1);
}
函数声明:
alert(fn2); //function
function fn2() {
alert(2);
}
三、对象和函数都是引用的关系
var a = 5;
var b = a;
b += 3;
alert(b);//8
alert(a);//5
对象引用:
var a = [1,2,3];
var b = a;//a把内存地址也给了b,a和b共用一个地址
b.push(4);
alert(b);//1,2,3,4
alert(a);//1,2,3,4
var a = [1,2,3];
var b = a;//a把内存地址也给了b,a和b共用一个地址
b = [1,2,3,4];//b在内存当中又重新占了一个地址,与a分离,与之前的b不同,修改b也不会影响到a了。
alert(b);//1,2,3,4
alert(a);//1,2,3
var obj = {
a:10
};
var obj2 = obj;
obj2.a = 20;
alert(obj.a);//20
四、事件委托
事件委托(事件代理):利用冒泡原理
event对象的事件源:不管在哪个事件中,只要你操作的那个元素就是事件源
ie:window.event.srcElement
标准下:event.target
nodeName:找到当前元素的标签名
好处:
1)提高性能
例子:移到li使标签变颜色。移出颜色消失。移到ul身上没反应。
window.onload = function(){
var oUl = document.getElementById(‘ul‘);
var oLi = oUl.getElementsTageName(‘li‘);
/*for(var i = 0; i < oLi.length;i++){
oLi[i].onclick = function (){
alert(123);
}
}*/
//事件委托:
oUl.onmouseover = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase()==‘li‘){
target.style.background = ‘red‘;
}
}
oUl.onmouseout = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
target.style.background = ‘ ‘;
}
}
2)新添加的元素还会有之前的事件
window.onload = function(){
var oUl = document.getElementById(‘ul‘);
var oLi = oUl.getElementsTageName(‘li‘);
var iNow = 4;
oUl.onmouseover = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase()==‘li‘){
target.style.background = ‘red‘;
}
}
oUl.onmouseout = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
target.style.background = ‘ ‘;
}
//点击一次按钮后会在ul中新添加一个li,当不使用事件委托,直接在li上添加鼠标 移入移出事件,那么新添加的li元素就不会 存在之前li的鼠标移入移出事件。如果使 用事件委托,之前的li事件效果也会添加到新添加的li元素身上,原理就是利用了冒 泡。
button.onclick = function(){
iNow++;
var oLi = document.createElement(‘li‘);
oLi.innerHTML = 1111* iNow;
oUl.appendChild(oLi);
}
}
五:排序:
快速排序:
1、找一个处于中间位置的基准点
2、建立两个数组,分别存储左边和右边的数组。比基准小的放前面,比基准大的放后面。
3、利用递归进行下次比较
递归:一个函数进入下一个这个函数又进入,遇到条件返回上一个,一层一层返回到第一个。
1)函数调用函数自身,执行递的动作
2)最后一次判断一个终止条件,可以执行归的动作。
递归求阶乘:
function test(n){
if(n==1){//当到1时,结束递的过程,开始倒着进行归过程
return 1;//一层层返回,最后一次返回到上一次的函数...
}
return n*test(n-1);// 4x3x2
}
alert(test(4));
快排:
function quickSort(arr){
if(arr.length<=1){ //数组为空或者有一个数
return arr;
}
var num = Math.floor(arr.length/2);//获取中间位置的索引
var numValue = arr.splice(num,1);//把中间位置的基准点分离出来
var left = [];
var right = [];
for(var i =0;i<arr.length;i++){
if(arr[i]<numValue){//如果小于基准点,放在left[]中
left.push(arr[i]);
}else{
right.push(arr[i]);
}
}
//递归第二次是根据第一次基准点分成的左右,分别在左边和右边各找个中间的基准点,再次划分left和right...
return quickSort(left).concat([numValue],quickSort(right));//合并,递归
}
alert(quickSort([12,5,37,6,22,40]);)//5,6,12,22,37,40
冒泡排序:
for (var i=0; i<arr.length-1; i++) {
var f = false;
for (var j=0; j<arr.length-1-i; j++) {
var a = arr[j];
var b = arr[j+1];
if (a < b) {
f = true;
arr[j] = b;
arr[j+1] = a;
}
}
if (!f) {
break;
}
alert(arr);
}
六、枚举算法
枚举算法:
从众多的候选答案中用for来做筛选,通过if找出正确的解
七、JS的跨域:
同一个域名下的js文件不存在跨域问题
ajax不能跨域
解决跨域问题:
1)子域和主域都设置document.domain = ‘a.com‘;
2)服务器代理:XMLHttpRequest代理文件
3)script标签:jsonp的形式(单域操作)
jsonp: json + padding(内填充)
在a网站:在a网站调用b网站
<script>
function box(json){
alert(son.name); //leo
}
</script>
//jsonp在调用函数js的下面调用:
<script src="jsonp.js"></script>
在b网站:jsonp.js:
<script>
box({name = ‘leo‘});
</script>
4)location.hash
5)window.name
6)flash
7)html5 postMessage
八、iframe:
1)在主页面操作iframe页面中元素方法:
var oIframe = .....
所有浏览器都支持:
oIframe.contentWindow.document.getElementById(‘div‘)...
或者:
ie 6 7不支持:
oIframe.contentDocument.getElementById(‘div‘)...
2)在iframe页面中操作父级页面元素方法:
window.parent.document.getElementById(‘div‘)...//父层页面
window.top.document.getElementById(‘div‘)...//最顶层页面
ie下iframe的unload事件只能用绑定的形式
九、console:
console.log()
console.warn();//警告提示
console.error();//错误提示
console.group()//分组开始
console.groupEnd();//分组结束
分组开始和结束之间一般添加console.log()进行调试
console.dir();输出对象所有信息
console.dirxml();显示当前元素包含的代码结构
console.assert();如果是假的断言失败,如果是真的断言不提示信息
console.trace();当前函数的执行过程
console.time(‘计时器‘);代码执行时间。括号中写个标题,表示开始。
...代码
console.timeEnd(‘计时器‘);表示时间结束。括号中写的内容与开始时一样
console.profile();测试代码性能,括号中可以为空。
...代码
console.profileEnd();
十、DOM 优化:
1、dom与JavaScript:
尽量减少js操作dom 的次数
innerHTML与dom方法:
如果要创建多个元素li,在for循环前面声明一个变量:var str =" "; 然后在for循环里用str += ‘<li></li>‘;后在for循环后,添加到ul.innerHTML = str;
var ul = document.get ...
var str = " ";
for(var i =0;i<3000;i++){
str += ‘<li></li>‘;
}
ul.innerHTML = str;
2、减少dom操作
1)节点克隆 - cloneNode
var ul = doc...
var li = document.createElement(‘li‘);
ul.innerHTML = ‘li‘;
for(var i = 0;i<3000;i++){
var new = li.cloneNode(true);
ul.appendChild(new);
}
2)访问元素集合 - 尽量用局部变量
var lei = li.length;//优化
for(var i = 0;i<len;i++){
li[i]....
}
3)元素节点 - 尽量用只获取元素的节点方法
childNodes:能获取元素节点、文本节点
children:只能获取元素节点,推荐使用
firstChild:能获取元素节点、文本节点
firstElementChild:只能获取元素节点,推荐使用
4)选择器API
querySelector、querySelectorAll
3、dom与浏览器:
重排:改变页面的内容(改变元素的宽高和位置)
重绘:浏览器显示内容(重排后的显示就是重绘)
改变元素的背景颜色是重绘
1)添加顺序 - 尽量在appendChild前添加操作
2)合并dom操作 - 利用cssText
3)缓存布局信息 - 把操作用变量先保存起来
4)文档碎片 - createDocumentFragment();创建少的话提高不大
var ul = document.get...
var frag = document.createDocumentFragment();
for(var i = 0 ; i <3000; i++){
var li = document.createElement(‘li‘);
frag.appendChild(li);
}
ul.appendChild(frag);
4、dom与事件的关系:
可以用事件委托
5、dom与前端模板:
能更好地对逻辑和视图分离,MVC架构的基础
十一、变量预解析
alert(a);
//在很多的其他语言中,变量需要先申明再使用,但是在js中我们可以先使用再申明(虽然这里的结果不是10,是undefined)
var a = 10;
//在js中,js解析器会对我们的代码做一些初始化分析的工作,其他包含了这么一项内容,解析器会把程序中变量的申明在程序代码执行之前做一个初始化, 解析器会把变量的申明提前处理,上面的先调用,后申明其实也可以是下面这样
变量预解析:
var a; //申明会提前,赋值不会提前
alert(a);
a = 10;//赋值
作用域:
var a = 10;
function fn() { //函数申明
//var a;
alert(a); //函数内部的a
var a = 100; //申明被提前到了当前作用域的最开始
alert(a);
}
fn(); //undefined 100
十二、callee 和 caller
callee 返回正在执行的函数本身的引用,它是arguments的一个属性
1 这个属性只有在函数执行时才有效
2 它有一个length属性,可以用来获得形参的个数,因此可以用来比较形参和实参个数是否一致,即比较arguments.length是否等于arguments. callee.length
3 它可以用来递归匿名函数。
function fn() {
//arguments.callee => 当前函数
alert(arguments.callee);// function fn(){}
}
eg:让setTimeout来模拟setInterval
函数递归方法:
var a = 1;
function interval() {
setTimeout(function() {
document.title = a++;
interval();
}, 100);
}
interval();
利用闭包自执行和callee来递归:
var a = 1;
(function() {
var _arguments = arguments;
setTimeout(function() {
document.title = a++;
_arguments.callee(); //_arguments代表之前定义的父级函数的
}, 100);
})();
caller:返回一个函数的引用,这个函数调用了当前的函数。即调用该函数的函数。
使用这个属性要注意:
1 这个属性只有当函数在执行时才有用
2 如果在javascript程序中,函数是由顶层调用的,则返回null
functionName.caller: functionName是当前正在执行的函数。
function fn1() {
console.log(1);
console.log(fn1.caller);//fn2
}
function fn2() {
console.log(2);
console.log(fn2.caller); //null
fn1();
}
fn2(); //2 -> null -> 1 -> fn2
十三、call 和 apply
call和apply都是能改变函数内部this的指向
function fn1(a, b) {
console.log(this);
console.log(a + b);
}
fn1.call(document, 1, 2);// 第一个参数是this的指向,可以为null,其后的都是该函数的参数
apply 和call基本一致,不同的是参数的传递上
fn1.apply(document, [1, 2]); //第二个参数就是fn1中的arguments
求最大值:
var arr = [1,6,4,8,3,10,2];
//console.log( Math.max(1,6,4,8,3,10,2) ); //arguments => [1,6,4,8,3,10,2]
console.log( Math.max.apply(null, arr) ); //arguments => arr
十四、this
this
* 函数外 : window
* 函数内 :
* 当一个函数被对象调用,则该函数内的this指向调用该函数的对象
* 当一个函数被事件调用,则该函数内的this指向触发该事件的对象
* 通过call、apply去调用一个函数,那么这个函数指向call、apply的第一个参数(如果call、apply的第一个参数是undefined/null,则this指向调用该函数的对象)
十五、优化
减少全局变量:作用域链
只加载可视区内容
减少dom操作:事件委托 、文档碎片
减少请求和质量:合并JS 、压缩JS
能使用正则尽量使用正则
十六、变量的作用域:
1、外层的变量,内层可以找到(全局);内层的变量,外层找不到(局部)。
var a = 10;
function aaa(){
alert(a);
}
function bbb(){
var a = 20;
aaa();
}
bbb(); // 10
2、当var 不加的时候,会自动生成全局的变量(不建议这样写。最好把所有要定义的变量加上var)。
function aaa(){
var a = b = 10; // 拆分为b = 10; var a = 10;
}
aaa();
alert(a); // undefined
alert(b); // 10;
3、变量的查找是在代码逐行解读下就近原则去寻找var定义的变量或者function的参数 ,当就近没找到就到父级去找。
var a = 0;
function aaa(){
alert(a); // undefined;代码逐行解读
var a = 20;
}
aaa();
```
var a = 10;
function aaa(){
a = 20;
alert(a); //20
}
aaa();
```
var a = 0;
function aaa(){
alert(a); //10
a = 20;
}
aaa();
```
var a = 10;
function aaa(){
bbb();
alert(a);//10
function bbb(){
var a = 20;
}
}
4、当参数和局部变量重名的时候,优先级是等同的。
var a = 10;
function aaa(a){
alert(a); //10
var a = 20;
}
aaa(a);
5、基本类型的赋值,只是值的改变;对象之间是引用的关系,在内存中地址是相同的。
var a = 20;
function aaa(a){
a+=3;
}
aaa(a);
alert(a);//10 函数中的a和外部的a不同
···
var a = [1,2,3];
function aaa(a){
a.push(4); // 修改了外部a
}
aaa(a);
alert(a); // 1,2,3,4
···
var a = [1,2,3];
function aaa(a){
a = [1,2,3,4]; //在内存中重新生成了一个a,与外部的a不同
}
aaa(a);
alert(a); // 1,2,3