javascript经典问题总结及代码实例(未完待续)
函数
匿名函数立即调用
(function (){alert(1)}())
function的左圆括号是必须的
函数的调用
1,作为函数的调用,
2,作为方法的调用
嵌套函数的this没有作用域的限制,如果作为方法调用,指调用它的对象,如果作为函数调用,this是全局对象或undefined
var o={ m:function(){ var self=this; console.log(this===o) f() function f(){ console.log(this===o)//false console.log(self===o)//true } } }
3,作为构造函数的调用
创建空对象,对象的原型是构造函数的prototype,构造函数使用this引用这个新对象,那么 var a=new o.m()中的m中的this就不是o而是a
4,间接调用
bind apply call的区别
apply和call是对函数的调用,第一个参数是改变this的指向的对象,call第二个以上传的是每个参数,apply第二个参数是数组,将要传递的参数写在数组里
两个方法使得任何函数可以作为任意对象的方法来调用
bind的第一个参数是用来改变原函数this的指向,返回一个新函数,不进行调用
function f(y){return this.x+y} var o={x:1} var g=f.bind(o) g(2)//3
柯里化
var sum=function(x,y){return x+y} var suc=sum.bind(null,1) suc(2)//3
关于this
函数里的this,谁调用它就是谁,作为谁的构造函数就是谁,通过apply call可以改变
什么是作用域
js中每个函数是一个作用域,在函数中声明的变量和函数的参数在整个函数体包括其嵌套函数都是可见的
var scope="gl" function ch(){ var scope="cb" function ne(){ var scope='ne' return scope } return ne() } ch()//ne // function test(o){ var i=0; if(typeof o=='object'){ var j=0; for(var k=0;k<10;k++){ console.log(k) } console.log(k) } console.log(j) } test({a:7})//i k j都是在该函数作用域内有定义的
声明提前,只要变量在函数内定义了,这个变量在该作用域的任意地方都可用,甚至在变量之前的位置
// var scope='glo' function f(){ console.log(scope) var scope='local' console.log(scope) } f()//undefined local
作用域链
变量层层向上向外的作用域寻找(函数的参数属于函数的局部变量)
函数递归
函数自己调用自己
什么是闭包
闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
下面就是我的学习笔记,对于Javascript初学者应该是很有用的。
一、变量的作用域
要理解闭包,首先必须理解Javascript特殊的变量作用域。
变量的作用域无非就是两种:全局变量和局部变量。
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
var n=999; function f1(){ alert(n); } f1(); // 999
另一方面,在函数外部自然无法读取函数内的局部变量。
function f1(){ var n=999; } alert(n); // error
这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
function f1(){ n=999; } f1(); alert(n); // 999
二、如何从外部读取局部变量?
出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。
那就是在函数的内部,再定义一个函数。
function f1(){ var n=999; function f2(){ alert(n); // 999 } }
在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
function f1(){ var n=999; function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999
三、闭包的概念
上一节代码中的f2函数,就是闭包。
各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
四、闭包的用途
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
怎么来理解这句话呢?请看下面的代码。
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert(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,可以在函数外部对函数内部的局部变量进行操作。
五、使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
六、思考题
如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。
代码片段一。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());
代码片段二。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()());
function cr(){ var n=0; return { count:function(){return n++}, reset:function(){n=0} } } var c=cr(),d=cr() c.count()//0 d.count()//0 c.reset() c.count()//0 d.count()//1
每次函数调用时,形成新的对象来保存局部变量。
垃圾回收
函数内的变量,在函数调用之后会被从内存删除,但如果函数内的嵌套函数被作为返回值,被某个变量引用,那么嵌套函数里的变量将不会被内存回收
对象
var y=x;如果x指向一个对象的引用,那么y也是,而非这个对象的复本,通过y修改对象,也会对x造成影响
创建对象的三种方式
1,直接量 var obj={a:1}
2,new 构造函数
3,Object.create(对象)
原型链
除了Object.prototype和null,任何对象都继承自另外一个对象,即原型,往上追溯,形成原型链(家谱)
继承的话,不会改变原型的属性值
function inherit(p){ if(p==null) throw TypeError() if(Object.create) return Object.create(p) var t=typeof p if(t!=='object' && t!=='function') throw TypeError() function f(){} f.prototype=p return new f() } var u={r:1} var c=inherit(u) c.x=1;c.y=1; c.r=3 console.log(u.r)//1
删除属性
delete删除可配置性为true的自有属性,不能删除继承属性
检测对象是否有属性的四个方法
1,in
var o={x:1} 'x' in o //true 'toString' in o//true
2,hasOwnProperty() 检测自有属性,对于继承属性返回false
3,propertyIsEnumerable()检测自有属性且该属性可枚举
4,o.x!==undefined
枚举属性
for in
Object.keys() 自有可枚举的属性
Object.getOwnPropertyNames() 自有属性不仅是可枚举的属性
getter&setter
var p={ x:1.0, y:1.0, get r(){ return this.x*this.y}, set r(v){//忽略set的返回值 if(v>0){ r=90 } }, get t(){ return 100 } }
属性的4个特性
Object.getOwnPropertyDescriptor({x:1},'x')
//{value:1,writable:true,enumerable:true,configurable:true}
修改4个特性
var o={}
Object.defineProperty(o,'x',{value:1,writable:true,enumerable:false,configurable:true})
var p=Object.defineProperties({},{x:{value:1},y:{writable:true}})
可配置的优先级最高
对象的属性
原型属性
Object.getPrototypeOf()查询原型
isPrototypeOf()是否是它的原型(和instanceof类型,构成判断是否是对象的方法)
类属性
Object.prototype.toString.call(obj).slice(8,-1)
可扩展性
事件
事件冒泡:由最具体的元素(嵌套层次最深的)接收,然后逐级向上传到不具体的节点
事件捕获:过程相反,
DOM2级事件流:事件捕获=》处于目标=》事件冒泡
事件的写法:
1,行内元素
注意some和行内在同一作用域里
<div id="dom" onclick="alert('de')">点击我</div> <div onclick="some()">点击ta</div> <script> function some(){ alert('ta') } </script>
2,DOM0级事件
onclick=function(){}//添加
onclick=null//移除
3,DOM2级事件
addEventListener('click',fn,true捕获阶段调用|false冒泡阶段)
大多是是冒泡阶段
removeEventListener移除的函数必须等于添加的函数,这时通过单独函数定义实现
4,事件兼容写法
var EventUtil = { addHandler: function(element, type, handler){ if (element.addEventListener){ element.addEventListener(type, handler, false); } else if (element.attachEvent){ element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, removeHandler: function(element, type, handler){ if (element.removeEventListener){ element.removeEventListener(type, handler, false); } else if (element.detachEvent){ element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } }, getEvent: function(event){//获取事件对象 return event ? event : window.event; }, getTarget: function(event){ return event.target || event.srcElement; }, preventDefault: function(event){//阻止默认事件 if (event.preventDefault){ event.preventDefault(); } else { event.returnValue = false; } }, stopPropagation: function(event){//阻止冒泡 if (event.stopPropagation){ event.stopPropagation(); } else { event.cancelBubble = true; } } };
理解事件函数里的this target currentTarget
点击页面的#mybtn按钮
document.body.onclick=function(event){ alert(event.currentTarget===document.body)///true alert(this===document.body)//true alert(event.target===document.getElementById('mybtn'))//true }
currentTarget始终和对象this相同,target则是事件的实际目标
js进程
js是单线程的,除了主js执行进程外,还有代码队列,随着时间,代码按照顺序添加到队列,主进程执行完后,是空闲状态,后面的进程开始执行。
定时器例子
假设,某个onclick 事件处理程序使用setInterval()设置了一个200ms 间隔
的重复定时器。如果事件处理程序花了300ms 多一点的时间完成,同时定时器代码也花了差不多的时间,
就会同时出现跳过间隔且连续运行定时器代码的情况
这个例子中的第1 个定时器是在205ms 处添加到队列中的,但是直到过了300ms 处才能够执行。当
执行这个定时器代码时,在405ms 处又给队列添加了另外一个副本。在下一个间隔,即605ms 处,第一
个定时器代码仍在运行,同时在队列中已经有了一个定时器代码的实例。结果是,在这个时间点上的定
时器代码不会被添加到队列中。结果在5ms 处添加的定时器代码结束之后,405ms 处添加的定时器代码
就立刻执行。
为了避免setInterval()的重复定时器的这2个缺点,你可以用如下模式使用链式setTimeout()
调用。
setTimeout(function(){ //处理中 setTimeout(arguments.callee, interval); }, interval);
setTimeout(function(){ var div = document.getElementById("myDiv"); left = parseInt(div.style.left) + 5; div.style.left = left + "px"; if (left < 200){setTimeout(arguments.callee, 50); } }, 50);
http
let xhr=new XMLHttpRequest(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){//已接收到全部的相应数据 if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){//304表示请求的资源没被改,直接用浏览器的缓存 alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("get", "example.txt", true);//是否异步 true异步 false同步 xhr.send(null); `` 默认情况下,在发送XHR 请求的同时,还会发送下列头部信息。 Accept:浏览器能够处理的内容类型。 Accept-Charset:浏览器能够显示的字符集。 Accept-Encoding:浏览器能够处理的压缩编码。 Accept-Language:浏览器当前设置的语言。 Connection:浏览器与服务器之间连接的类型。 Cookie:当前页面设置的任何Cookie。 Host:发出请求的页面所在的域 。 Referer:发出请求的页面的URI。注意,HTTP 规范将这个头部字段拼写错了,而为保证与规 范一致,也只能将错就错了。(这个英文单词的正确拼法应该是referrer。) User-Agent:浏览器的用户代理字符串。 虽然不同浏览器实际发送的头部信息会有所不同,但以上列出的基本上是所有浏览器都会发送的。 经常设置的请求头是
xhr.setRequestHeader('Content-Type', 'application/json')
//设置header的需要放在open之后 请求参数为JSON.stringify({})
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
//如果请求参数需序列化为查询字符串
请求参数序列化的方法
1,使用new FormData()的方便之处体现在不必明确地在XHR 对象上设置请求头部。XHR 对象能够识别传
入的数据类型是FormData 的实例,并配置适当的头部信息。
2,qs.stringify(json)
3,表单序列化serialize(document.getElementById("user-info"))
4,直接传'a=b&c=d'
文件postexample.php 就可以通过$_POST 取得提交的数据了:
<?php header("Content-Type: text/plain"); echo <<<EOF Name: {$_POST[‘user-name’]} Email: {$_POST[‘user-email’]} EOF; ?>
如果不设置Content-Type 头部信息,那么发送给服务器的数据就不会出现在$_POST 超级全局变
量中。这时候,要访问同样的数据,就必须借助$HTTP_RAW_POST_DATA。
数据类型
5种
undefined、null、boolean、number、string、object。
typeof判断返回值
"undefined"——如果这个值未定义;
"boolean"——如果这个值是布尔值;
"string"——如果这个值是字符串;
"number"——如果这个值是数值;
"object"——如果这个值是对象或null;
"function"——如果这个值是函数。
alert(null == undefined); //true
数据类型 转换为true的值 转换为false的值
Boolean true false
String 任何非空字符串 ""(空字符串)
Number 任何非零数字值(包括无穷大) 0和NaN
Object 任何对象 null
Undefined n/a undefined
NaN与任何数值都不相等,包括本身
NaN==NaN //false
Number()函数的转换规则如下。
如果是Boolean 值,true 和false 将分别被转换为1 和0。
如果是数字值,只是简单的传入和返回。
如果是null 值,返回0。
如果是undefined,返回NaN。
如果是字符串,遵循下列规则:
如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即"1"
会变成1,"123"会变成123,而"011"会变成11(注意:前导的零被忽略了);
如果字符串中包含有效的浮点格式,如"1.1",则将其转换为对应的浮点数值(同样,也会忽
略前导零);
如果字符串中包含有效的十六进制格式,例如"0xf",则将其转换为相同大小的十进制整
数值;
如果字符串是空的(不包含任何字符),则将其转换为0;
如果字符串中包含除上述格式之外的字符,则将其转换为NaN。
如果是对象,则调用对象的valueOf()方法,然后依照前面的规则转换返回的值。如果转换
的结果是NaN,则调用对象的toString()方法,然后再次依照前面的规则转换返回的字符
串值。
根据这么多的规则使用Number()把各种数据类型转换为数值确实有点复杂。下面还是给出几个具
体的例子吧。
var num1 = Number("Hello world!"); //NaN
var num2 = Number(""); //0
var num3 = Number("000011"); //11
var num4 = Number(true); //1
parseInt()
它会忽略字
符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt()
就会返回NaN;也就是说,用parseInt()转换空字符串会返回NaN(Number()对空字符返回0)。如
果第一个字符是数字字符,parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了
一个非数字字符。例如,"1234blue"会被转换为1234,因为"blue"会被完全忽略。类似地,"22.5"
会被转换为22,因为小数点并不是有效的数字字符。
如果字符串中的第一个字符是数字字符,parseInt()也能够识别出各种整数格式(即前面讨论的
十进制、八进制和十六进制数)。也就是说,如果字符串以"0x"开头且后跟数字字符,就会将其当作一
个十六进制整数;如果字符串以"0"开头且后跟数字字符,则会将其当作一个八进制数来解析。
toString()
null和undefined没有该方法
数组
var a=['s','d'] a[99]='c' a.length//100 //其他项是undefined
数组的方法
Array.isArray()// true false
sort(比较函数)方法
比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等
则返回0,如果第一个参数应该位于第二个之后则返回一个正数
concat(单个值或数组,...) 数组的副本
正则
实例属性
g
i
m
转义:模式中使用的所有元字符都必须转义 ( [ { ^ $ | ) ? * + .]}
字面量:不加字符串 转义\
构造函数:字符串形式 。所有元字符都必须双重转义 \
(字符在字符串中通常被转义为\,而在正则表达式字符串中就
会变成\\)
/w\hello\123/ "\w\\hello\\123"
var re = null, i; for (i=0; i < 10; i++){ re = /cat/g; re.test("catastrophe"); } for (i=0; i < 10; i++){ re = new RegExp("cat", "g"); re.test("catastrophe"); }
实例方法
正则.exec(字符串)
.test
构造函数形式.toString() //字面量
构造函数形式.toLocaleString() //字面量
字符串方法
.search()
.match()
.replace()
符号