原生JS笔记总结
<script> /*1.如何区分私有变量和全局变量? 1)在全局作用域下声明(预解释的时候上)的变量是全局变量 2)在“私有作用域中声明的变量”和“函数的形参”都是私有变量 在私有作用域中,我们代码执行的时候遇到了一个变量,首先我们需要确定它是否为私有的变量,如果是私有的变量,那么和外面的没有任何的关系;如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直找到window为止 ...(作用域链) 2.当函数执行的时候(直接目的:让函数体中的代码执行),首先会形成一个新的私有的作用域,然后按照如下的步骤执行: 1)如果没有形参,先给形参赋值 2)进行私有作用域中的预解释 3)私有作用域中的代码自上而下执行 ... 4)函数形成一个新的私有的作用域保护了里面的私有变量不受外界干扰(外面修改不了里面的),私有的也修改不了外面的-》闭包 闭包:闭包是一种机制,函数执行的时候形成了一个私有的作用域,保护里面的私有变量不受外界干扰。 //预解释的时候不管你的条件是否成立,都要把带var的进行提前的声明 //window的预解释:var num; -> window.num if(!('num' in window)){ var num = 12; } console.log(num) // undefined 3.执行函数定义的那个function在全局作用域下不进行预解释,当代码执行到这个位置的时候定义和执行一起完成了 // 自执行函数:定义和执行一起完成了,不进行预解释 (function(num){})(100) ~function(num){}(100 +function(num){}(100) -function(num){}(100) !function(num){}(100) 4.函数体中的return下面的代码虽然不在执行了,但是需要进行预解释;return后面跟着的都是我们的返回值,所以不进行预解释。 function fn(){ console.log(num); // undefined return function(){ }; var num = 10; } fn(); 5.在预解释的时候,如果名字已经声明过了,不需要重新声明,但是需要重新赋值;在JS中如果变量的名字和函数的名字重复了,也算冲突 预解释:var fn; window.fn; fn = xxxfff000 window.fn = xxxfff000 var fn = 10; function fn(){ console.log('ok'); } //window预解释: //声明+定义 fn = xxxfff11 //声明 var fn;(不需要重新声明) //声明(不重复进行) + 定义 fn = xxxfff222 //->fn=xxxfff222 fn(); function fn(){ console.log(1) } fn(); var fn = 10; fn(); function fn(){ console.log(2) } fn(); //如何查找当前作用域上一级作用域 //看当前函数是在哪个作用域下定义的,那么它的上级作用域就是谁->和函数在哪执行的没有任何关系 var num = 12; function fn(){ var num = 120; return function(){ console.log(num); }; } var f = fn(); f(); //120 ~function(){ var num = 1200; f() //120 } // 堆内存 // 对象数据类型或者函数数据类型在定义的时候首先会开辟一个堆内存,堆内存有一个引用的地址,如果外面有变量等引用了这个地址,我们就说这个内存被占用了,就不能销毁了。 我们要让堆内存释放/销毁,只需要把这个引用它的变量赋值为null。如果当前的堆内存没有任何东西被占用了,那么浏览器会在空闲的时候把它销毁。。。 //销毁: obj = null: //栈内存 1)全局作用域 只有当页面关闭的时候全局作用域才会销毁 2)私有的作用域(只有函数执行会产生私有的作用域) //一般情况下,函数执行会形成一个新的私有作用域,当私有作用域中的代码执行完成后,我们当前作用域都会主动的进行释放和销毁 //但是还是存在特殊情况的: //当前私有作用域中的部门内存被作用域以外的东西占用了,那么当前的这个作用域就不能销毁了 //a,函数执行返回了一个引用数据类型的值,并且在函数的外面被一个其他的东西给接收了,这种情况下一般形成的私有作用域都不会销毁。 function fn(){ var num = 100; return function(){ num++; console.log(num); } } var f = fn(); f(); 下述情况属于不立即销毁->fn返回的函数没有被其他的东西占用,但是还需要执行一次呢,所以暂时不销毁,当返回的值执行完成后,浏览器会在空闲的时候把它销毁了。->“不立即销毁” function fn(){ var num = 100; return function(){ num++; console.log(num); } } fn()(); 检测数据类型的四种方式: 1.typeof 用来检测数据类型的运算符 2.instanceof 检测某一个实例是否属于某个类 3.constructor 构造函数 4.Object.prototype.toString.call() 1.for,while 都是同步模式 2.异步: 不会立即执行,需要等一定时间,只有当下面的事情都处理完成了,才会回头处理之前的事情;如果下面的事情并没有处理完成,不管之前的事情有没有到时间,都等着。 3.在JS中异步编辑有四种情况: 定时器 事情绑定 Ajax 回调函数 每一个浏览器对于定时器的等待时间都有一个最小的值,谷歌5-6ms,IE:10-13ms,如果设置的等待时间小于这个值,不起作用,还需要等到最小时间才执行;0也不是立即执行。 var n = 0; setTimeout(function(){ n++; console.log(n) },0) console.log(n) 我们定时器设置的等待时间不一定是最后执行时间,如果定时器之后还有其他的事情正在处理中,不管定时器的时间有没有到,都是不会执行定时器的 var n = 0; setTimeout(function(){ n++; console.log(n) //不执行 },0) console.log(n) //1 while(1){ //死循环 n++; } console.log(n); //不执行 数组forEach不支持return,没有返回值 数组map支持return,有返回值,并不影响原来的数组,相当于把原数组克隆一份返回出去 var obj = {name:'hongliang'} var ary = [1,2,3,4,5]; //->forEach方法中的this是ary;匿名回调函数中的this默认是window; ary.forEach(function(item,index){ console.log(this); //window },obj);//->不管是forEach还是map都支持第二个参数值,第二个参数的意思是把匿名回调函数中的this进行修改 var obj = {name:'hongliang'} var ary = [1,2,3,4,5]; ary.forEach(function(item,index){ console.log(this); //window }.call(obj)) //->给forEach赋值的时候,首先把匿名函数执行,把匿名函数中的this改变成obj,把匿名函数执行的返回结果undefined赋值给forEach(不行); var obj = {name:'hongliang'} var ary = [1,2,3,4,5]; ary.forEach(function(item,index){ console.log(this); //obj }.bind(obj)) //->兼容性(ie8及以下) 我们在js中主要研究的都是函数中的this; JS中的this代表的是当前行为执行的主体;JS中的context代表的是当前行为执行的环境(区域) this 是谁和函数在哪定义的和在哪执行的都没有任何的关系:如何区分this呢? 1,函数执行,首先看函数名前面是否有".",有的话,"."前面是谁this就是谁;没有的话this就是window 2,自执行函数中的this永远是window 3,给元素的某一个事件绑定方法,当事件执行时,this是绑定的元素 4,全局作用下,自定义执行函数中不预解析 function fn(){ console.log(this); } var obj = {fn:fn}; fn(); //this->window obj.fn(); //this->obj document.getElementById('div1').onclick = fn; //this->#div1 document.getElementById('div1').onclick = function(){ //this->#div1 fn(); //this->window }; function sum(){ //this->window fn(): //this->window } sum(); var oo = { sum:function(){ //this->oo fn() //this->window } } oo.sum(); var num = 20; var obj = { num:30, fn:(function(num){ this.num *= 3; num += 15; var num = 45; return function(){ this.num *= 4; num += 20; console.log(num); } })(num); }; var fn = obj.fn; fn(); //65 obj.fn(); //85 console.log(window.num,obj.num) //240,120 作用域不销毁的三种情形: 1,函数执行返回一个引用类型,被一个变量接收 2,在私有作用域里给元素绑定一个方法 3,fn()(); 1, var count = 0; obtn.onclick = function(){ count++; console.log(count); } 2, ~function(){ var count = 0; obtn.onclick = function(){ count++; console.log(count); } }() obtn.onclick = (function(){ var count = 0; return function(){ count++; console.log(count); } })(); 弊端:有一个不销毁的私有作用域,所以占那么一点内存。 3,利用innerHTML的方式处理:每点击一次都去页面获取最新的值,累加,最后再赋值过去 obtn.onclick = function(){ spanNum.innerHTML++; } 弊端:每一次都把页面中的内容转换为字符串再累加,累加完再重新添加回去,浏览器会重新渲染一下 4,利用自定义属性存储(推荐使用) obtn.count = 0; obtn.onclick = function(){ spanNum.innerHTML = ++this.count; } 所有的编程语言都是面向对象开发的->类的继承、封装、多态 继承:子类继承父类中的属性和方法 多态:当前方法的多种形态->后台语言中:多态包含重载和重写 JS中不存在重载,方法名一样的话,后面的会把前面的覆盖掉。 JS中有一个操作类似重载但不是重载:“我们可以根据传递的参数不一样,实现不同的功能。 重写:子类重写父类的写法 工厂模式: function createPerson(){ var obj = new Object(); obj.name = 'zhl'; obj.getName = function(){ return this.name; } return obj; } 构造函数模式: function createPerson(){ this.name = 'zhl'; this.getName = function(){ return this.name; } } //构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立开的->实例识别 //基于构造函数模式的原型模式解决了 方法或者属性公有的问题->把实例之间相同的属性和方法提取成公有的属性和方法->类.prototype = fn; *1.每一个函数数据类型(普通函数、类)都有一个天生自带的属性:prototype(原型),并且这个属性是一个对象数据类型的值 2.并且在prototype上浏览器天生给它增加了一个属性constructor(构造函数),属性值是当前函数(类)的本身 3.每一个对象数据类型(普通对象、实例、prototype...)也天生自带一个属性:__proto__,属性值是当前实例所属类的原型(prototype). 2、Object是JS中所有对象数据类型的基类(最基层的类) 1),f1 instanceof Object ->true 通过__proto__可以向上级查找,不管有多少级,最后总能找到Object. 2),在Object.prototype上没有__proto__这个属性 3、原型链模式 f1.hasOwnProperty("x");//->hasOwnProperty是f1的一个属性 但是我们发现在f1的私有属性上并没有这个方法,那如何处理的呢? 1)通过 对象名.属性名 的方式获取属性值的时候,首先在对象的私有属性上进行查找,如果私有属性中存在这个属性,则获取的是这私有属性的值。 2)如果私有属性上没有,则通过__proto__找到所属类的原型(类的原型上定义的属性和方法都是当前实例公有的属性和方法),原型上存在的话,获取的是公有属性值。 3)如果原型上也没有,则继续通过原型上的__proto__继续向上查找,一直找到Object.prototype为止...如果还是找不到则是undefined -->这种查找机制就是"原型链模式" 1、 JS中的所有的类都是函数数据类型,它通过new执行变成一个类,但是它本身也是一个普通的函数 JS中所有的实例都是对象数据类型的 2、构造函数中的this指当前new出来的实例对象 function Fn(){ //this->f1 this.x = 100; this.getX = function(){ //this->需要看getX执行的时候才知道 console.log(this.x); } } var f1 = new Fn; f1.getX(); //->方法中的this是f1->100 var ss = f1.getX; ss(); //->方法中的this是window -> undefined; 1、在构造函数模式中 new Fn() 执行,如果Fn不需要传递参数的话,后面的小括号可以省略 2、this的问题:在类中出现的this.xx = xxx中的this 都是当前类的实例,而某一个属性值(方法),方法中的this需要看方法执行的时候,前面是否有"."才知道this是谁。 匿名函数中的 this 是 window; 事件对象: e.pageX = e.pageX || (e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)) e.pageY = e.pageY || (e.clientY + (document.documentElement.scrollTop || document.body.scrollTop)) e.preventDefault() // 阻止默认行为 表格排序: function sortTable(n){ var _this = this; //-> 把存储所有行的类数组转化为数组 var ary = [].slice.call(oRows); //->点击当前列,让其它列flag回归到初始值-1;这样就不会乱; for(var k = 0; k<oThs.length; k++){ if(oThs[k] != this){ oThs[k].flag = -1; } } //->给数组进行排序; _this.flag *= -1; ary.sort(function(a,b){ var curInn = a.cells[n].innerHTML; var nextInn = b.cells[n].innerHTML; var curInnNum = parseFloat(a.cells[n].innerHTML); var nextInnNum = parseFloat(b.cells[n].innerHTML); if(isNaN(curInnNum) || isNaN(nextInnNum)){ return (curInn.localeCompare(nextInn)) * _this.flag; }else{ return (curInnNum - nextInnNum) * _this.flag; } }) //-> 按照ary中的最新顺序,把每一行重新添加到tBody中 var frg = document.createDocumentFragment(); for (var i = 0; i < ary.length; i++) { frg.appendChild(ary[i]); }; tBody.appendChild(frg); frg = null; } //点击排序:所有具有class="cursor" 这个样式的列都可以实现点击排序 for(var i = 0; i<oThs.length; i++){ var curTh = oThs[i]; if(curTh.className === "cursor"){ curTh.index = i; curTh.flag = -1; curTh.onclick = function(){ sortTable.call(this,this.index); } } } */ </script>