JavaScript札记

实现一个 sleep 函数javascript


====================================================================html


若是继承的属性是可遍历的,那么就会被for...in循环遍历到。可是,通常状况下,都是只想遍历对象自身的属性,因此使用for...in的时候,应该结合使用hasOwnProperty方法,在循环内部判断一下,某个属性是否为对象自身的属性。java


====================================================================
jquery

闭包(closure)是 Javascript 语言的一个难点,也是它的特点,不少高级应用都要依靠闭包实现。es6

理解闭包,首先必须理解变量做用域。前面提到,JavaScript 有两种做用域:全局做用域和函数做用域。函数内部能够直接读取全局变量。web


上面代码中,函数f1能够读取全局变量najax

可是,函数外部没法读取函数内部声明的变量。算法


上面代码中,函数f1内部声明的变量n,函数外是没法读取的。json

若是出于种种缘由,须要获得函数内的局部变量。正常状况下,这是办不到的,只有经过变通方法才能实现。那就是在函数的内部,再定义一个函数。跨域


上面代码中,函数f2就在函数f1内部,这时f1内部的全部局部变量,对f2都是可见的。可是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是 JavaScript 语言特有的"链式做用域"结构(chain scope),子对象会一级一级地向上寻找全部父对象的变量。因此,父对象的全部变量,对子对象都是可见的,反之则不成立。

既然f2能够读取f1的局部变量,那么只要把f2做为返回值,咱们不就能够在f1外部读取它的内部变量了吗!


上面代码中,函数f1的返回值就是函数f2,因为f2能够读取f1的内部变量,因此就能够在外部得到f1的内部变量了。

闭包就是函数f2,即可以读取其余函数内部变量的函数。因为在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,所以能够把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特色,就是它能够“记住”诞生的环境,好比f2记住了它诞生的环境f1,因此从f2能够获得f1的内部变量。在本质上,闭包就是将函数内部和函数外部链接起来的一座桥梁。

闭包的最大用处有两个,一个是能够读取函数内部的变量,另外一个就是让这些变量始终保持在内存中,即闭包可使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。


上面代码中,start是函数createIncrementor的内部变量。经过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中能够看到,闭包inc使得函数createIncrementor的内部环境,一直存在。因此,闭包能够看做是函数内部做用域的一个接口。

为何会这样呢?缘由就在于inc始终在内存中,而inc的存在依赖于createIncrementor,所以也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

闭包的另外一个用处,是封装对象的私有属性和私有方法。


上面代码中,函数Person的内部变量_age,经过闭包getAgesetAge,变成了返回对象p1的私有变量。

注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,因此内存消耗很大。所以不能滥用闭包,不然会形成网页的性能问题。

闭包的做用总结

一、可以访问函数定义时所在的词法做用域(阻止其被回收)。

二、私有化变量


三、模拟块级做用域


四、建立模块


模块模式具备两个必备的条件(来自《你不知道的JavaScript》)

  • 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会建立一个新的模块实例)
  • 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有做用域中造成闭包,而且能够访问或者修改私有的状态。

====================================================================

典型的“相似数组的对象”是函数的arguments对象,以及大多数 DOM 元素集,还有字符串。


上面代码包含三个例子,它们都不是数组(instanceof运算符返回false),可是看上去都很是像数组。

数组的slice方法能够将“相似数组的对象”变成真正的数组。

var arr = Array.prototype.slice.call(arrayLike);复制代码

====================================================================

避免数组处理方法中的this

数组的mapforeach方法,容许提供一个函数做为参数。这个函数内部不该该使用this


上面代码中,foreach方法的回调函数中的this,实际上是指向window对象,所以取不到o.v的值。缘由跟上一段的多层this是同样的,就是内层的this不指向外部,而指向顶层对象。

解决这个问题的一种方法,就是前面提到的,使用中间变量固定this


另外一种方法是将this看成foreach方法的第二个参数,固定它的运行环境。


====================================================================

JS的数据类型

1.基本类型(值类型或者原始类型): Number、Boolean、String、NULL、Undefined以及ES6的Symbol
2.引用类型:Object、Array、Function、Date等

在内存中的位置

  • 基本类型: 占用空间固定,保存在栈中

  • 引用类型:占用空间不固定,保存在堆中

栈(stack)为自动分配的内存空间,它由系统自动释放;使用一级缓存,被调用时一般处于存储空间中,调用后被当即释放。
堆(heap)则是动态分配的内存,大小不定也不会自动释放。使用二级缓存,生命周期与虚拟机的GC算法有关

当一个方法执行时,每一个方法都会创建本身的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将天然销毁了。所以,全部在方法中定义的变量都是放在栈内存中的;栈中存储的是基础变量以及一些对象的引用变量,基础变量的值是存储在栈中,而引用变量存储在栈中的是指向堆中的数组或者对象的地址,这就是为什么修改引用类型总会影响到其余指向这个地址的引用变量。

当咱们在程序中建立一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(由于对象的建立成本一般较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即便方法结束后,这个对象还可能被另外一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。

#赋值、浅拷贝、深拷贝

  • 对于基本类型值,赋值、浅拷贝、深拷贝时都是复制基本类型的值给新的变量,以后二个变量之间操做不在相互影响。

  • 对于引用类型值,

  • 赋值后二个变量指向同一个地址,一个变量改变时,另外一个也一样改变;

    浅拷贝后获得一个新的变量,这个与以前的已经不是指向同一个变量,改变时不会使原数据中的基本类型一同改变,但会改变会原数据中的引用类型数据

    深拷贝后获得的是一个新的变量,她的改变不会影响元数据

- 和原数据是否指向同一对象 第一层数据为基本数据类型 原数据中包含子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变不会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变不会使原数据一同改变 改变不会使原数据一同改变


##浅拷贝

数组经常使用的浅拷贝方法slice,concat,Array.from() ,以及es6的析构


对象经常使用的浅拷贝方法Object.assign(),es6析构


本身实现一个浅拷贝


##深拷贝

比较简单粗暴的的作法是使用JSON.parse(JSON.stringify(obj))


JSON.parse(JSON.stringify(obj)) 看起来很不错,不过MDN文档 的描述有句话写的很清楚:

undefined、任意的函数以及 symbol 值,在序列化过程当中会被忽略(出如今非数组对象的属性值
中时)或者被转换成 null(出如今数组中时)。
复制代码

JSON.parse(JSON.stringify(obj)) 的一些缺陷:

  1. 对象的属性值是函数时,没法拷贝。
  2. 原型链上的属性没法拷贝
  3. 不能正确的处理 Date 类型的数据
  4. 不能处理 RegExp
  5. 会忽略 symbol
  6. 会忽略 undefined

可是在平时的开发中JSON.parse(JSON.stringify(obj))已经知足90%的使用场景了。
下面咱们本身来实现一个


#参数的传递

全部的函数参数都是按值传递。也就是说把函数外面的值赋值给函数内部的参数,就和把一个值从一个变量赋值给另外一个同样;

基本类型


引用类型


不少人错误地觉得在局部做用域中修改的对象在全局做用域中反映出来就是说明参数是按引用传递的。
可是经过上面的例子能够看出若是person是按引用传递的最终的person.name应该是Tom。
实际上当函数内部重写obj时,这个变量引用的就是一个局部变量了。而这个变量会在函数执行结束后销毁。(这是是在js高级程序设计看到的,还不是很清楚)

#判断方法

基本类型用typeof,引用类型用instanceof

特别注意typeof null是"object", null instanceof Object是true;复制代码


Object.prototype.toString.call([]).slice(8, -1)有兴趣的同窗能够看一下这个是干什么的复制代码

#总结


====================================================================

#搞懂JSONP

script标签的src属性中的连接能够访问跨域的js脚本,利用这个特性,服务端再也不返回JSON格式的数据,而是返回一段调用某个函数的js代码,在src中进行了调用,这样实现了跨域。(不只如此,咱们还发现凡是拥有”src”这个属性的标签都拥有跨域的能力,好比<\script>、<\img>、<\iframe>)

##JSONP客户端具体实现

无论jQuery也好,extjs也罢,又或者是其余支持jsonp的框架,他们幕后所作的工做都是同样的,下面我来按部就班的说明一下jsonp在客户端的实现:

一、咱们知道,哪怕跨域js文件中的代码(固然指符合web脚本安全策略的),web页面也是能够无条件执行的。

远程服务器remoteserver.com根目录下有个remote.js文件代码以下:

alert('我是远程文件');复制代码

本地服务器localserver.com下有个jsonp.html页面代码以下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>
 
</body>
</html>复制代码

毫无疑问,页面将会弹出一个提示窗体,显示跨域调用成功。

二、如今咱们在jsonp.html页面定义一个函数,而后在远程remote.js中传入数据进行调用。

jsonp.html页面代码以下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript">
    var localHandler = function(data){
        alert('我是本地函数,能够被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
    };
    </script>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>
 
</body>
</html>复制代码

remote.js文件代码以下:

localHandler({"result":"我是远程js带来的数据"});复制代码

运行以后查看结果,页面成功弹出提示窗口,显示本地函数被跨域的远程js调用成功,而且还接收到了远程js带来的数据。
很欣喜,跨域远程获取数据的目的基本实现了,可是又一个问题出现了,我怎么让远程js知道它应该调用的本地函数叫什么名字呢?毕竟是jsonp的服务者都要面对不少服务对象,而这些服务对象各自的本地函数都不相同啊?咱们接着往下看。

三、聪明的开发者很容易想到,只要服务端提供的js脚本是动态生成的就好了呗,这样调用者能够传一个参数过去告诉服务端 “我想要一段调用XXX函数的js代码,请你返回给我”,因而服务器就能够按照客户端的需求来生成js脚本并响应了。

看jsonp.html页面的代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript">
    // 获得航班信息查询结果后的回调函数
    var flightHandler = function(data){
        alert('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票 ' + data.tickets + ' 张。');
    };
    // 提供jsonp服务的url地址(无论是什么类型的地址,最终生成的返回值都是一段javascript代码)
    var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
    // 建立script标签,设置其属性
    var script = document.createElement('script');
    script.setAttribute('src', url);
    // 把script标签加入head,此时调用开始
    document.getElementsByTagName('head')[0].appendChild(script); 
    </script>
</head>
<body>
 
</body>
</html>复制代码

此次的代码变化比较大,再也不直接把远程js文件写死,而是编码实现动态查询,而这也正是jsonp客户端实现的核心部分,本例中的重点也就在于如何完成jsonp调用的全过程。
咱们看到调用的url中传递了一个code参数,告诉服务器我要查的是CA1998次航班的信息,而callback参数则告诉服务器,个人本地回调函数叫作flightHandler,因此请把查询结果传入这个函数中进行调用。
OK,服务器很聪明,这个叫作flightResult.aspx的页面生成了一段这样的代码提供给jsonp.html

(服务端的实现这里就不演示了,与你选用的语言无关,说到底就是拼接字符串):

flightHandler({
    "code": "CA1998",
    "price": 1780,
    "tickets": 5
});复制代码

四、到这里为止的话,相信你已经可以理解jsonp的客户端实现原理了吧?剩下的就是如何把代码封装一下,以便于与用户界面交互,从而实现屡次和重复调用

jQuery如何实现jsonp调用?

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
     <title>Untitled Page</title>
      <script type="text/javascript" src=jquery.min.js"></script> <script type="text/javascript"> jQuery(document).ready(function(){ $.ajax({ type: "get", async: false, url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998", dataType: "jsonp", jsonp: "callback",//传递给请求处理程序或页面的,用以得到jsonp回调函数名的参数名(通常默认为:callback) jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也能够写"?",jQuery会自动为你处理数据 success: function(json){ alert('您查询到航班信息:票价: ' + json.price + ' 元,余票: ' + json.tickets + ' 张。'); }, error: function(){ alert('fail'); } }); }); </script> </head> <body> </body> </html>复制代码

是否是有点奇怪?为何我此次没有写flightHandler这个函数呢?并且居然也运行成功了!
这就是jQuery的功劳了,jquery在处理jsonp类型的ajax时(,虽然jquery也把jsonp纳入了ajax,但其实它们真的不是一回事儿),自动帮你生成回调函数并把数据取出来供success属性方法来调用,是否是很爽呀?
补充

这里针对ajax与jsonp的异同再作一些补充说明:

一、ajax和jsonp这两种技术在调用方式上”看起来”很像,目的也同样,都是请求一个url,而后把服务器返回的数据进行处理,所以jquery和ext等框架都把jsonp做为ajax的一种形式进行了封装。

二、但ajax和jsonp其实本质上是不一样的东西。ajax的核心是经过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加

====================================================================

实现数组去重

uniq([1, 2, 3, 5, 3, 2]); // [1, 2, 3, 5]

一、利用ES6新增数据类型Set

function uniq(arry) {
    return [...new Set(arry)];
}复制代码

二、利用indexOf

function uniq(arry) {
    var result = [];
    for(var i = 0; i < arry.length, i++){
        if(result.indexOf(arry[i]) === -1){
            result.push(arry[i]);
        }
    }
    return result;
}复制代码

三、利用includes

function uniq(arry) {
    var result = [];
    for(var i = 0; i < arry.length, i++){
        if(!result.includes(arry[i])){
            result.push(arry[i]);
        }
    }
    return result;
}复制代码

四、利用reduce

function uniq(arry){
    return arry.reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], [])
}复制代码

五、利用Map

function uniq(arry){
    let map = new Map();
    let result = new Array();
    for(let i - 0; i < arry.length; i++){
        if(map.has(arry[i])){
            map.set(arry[i], true);
        }else {
            map.set(arry[i], false);
            result.push(arry[i]);
        }
    }
    return result;
}复制代码

====================================================================

You will soon get something you have always desired.

相关文章
相关标签/搜索