# 原理 ## 数据类型 ```js window.onload = () => { var test1 = 'abcdef'; var test2 = 123; var test3 = true; var test4 = {}; var test5 = []; var test6; var test7 = { abcdef: 123 }; var test8 = ['abcdef', 123]; function test9() { return 'abcdef'; } var test10 = null; var test11 = 'q58'; var test12 = '张'; var test13 = '78'; console.log(typeof test1); // string console.log(typeof test2); // number console.log(typeof test3); // boolean console.log(typeof test4); // object console.log(typeof test5); // object console.log(typeof test6); // undefined console.log(typeof test7); // object console.log(typeof test8); // object console.log(typeof test9); // function console.log(typeof test10); // object // # 无法转为数字的为true console.log(isNaN(test11)); // true console.log(isNaN(test12)); // true console.log(isNaN(test13)); // false // 数值比较 console.log('1' - '1'); // 5个假值:undefined null “” 0 NaN // ** 字符串方法 ** // toLowerCase() 字符串大写 =>小写 // toUpperCase() 字符串小写 => 大写 // replace() 查找子串并将他们都替换为另一个字符串 // slice() 删除字符串的一部分并返回结果 // indexOf() 从头部开始查找子串,返回下标 // lastIndexOf() 查找最后一个子串,返回下标 // concat() 将字符串拼接起来 // match() 在字符串中查找与正则表达啥匹配的子串 // trim() 删除字符串开头和末尾的的空白字符, let phone = '123-8568'; function phoneF(number) { return number.match(/^\d{3}-?\d{4}$/); } console.log(phoneF(phone)); // 实例化对象 function Duck(sound) { this.sound = sound; this.quack = () => { console.log(this.sound); }; } let toy = new Duck('quack quack'); toy.quack(); console.log(typeof toy); // object console.log(toy instanceof Duck); // true }; ``` ## Object ### 判断 obj 对象 参考文章:[判断 JS 数据类型的 4 种方法](https://www.cnblogs.com/onepixel/p/5126046.html) typeof ```js /* typeof 是一个操作符,其右侧跟一个一元表达式, 并返回这个表达式的数据类型。 返回的结果用该类型的字符串(全小写字母)形式表示, 包括以下 7 种: number、boolean、symbol、string、 object、undefined、function 等。 */ typeof ''; // string 有效 typeof 1; // number 有效 typeof Symbol(); // symbol 有效 typeof true; //boolean 有效 typeof undefined; //undefined 有效 typeof null; //object 无效 typeof []; //object 无效 typeof new Function(); // function 有效 typeof new Date(); //object 无效 typeof new RegExp(); //object 无效 /* 其他正确,但不符合判断的结果 对于基本类型,除 null 以外,均可以返回正确的结果。 对于引用类型,除 function 以外,一律返回 object 类型。 对于 null ,返回 object 类型。 对于 function 返回 function 类型。 */ ``` instanceof - `instanceof` 认为能够判断出 [ ] 是 Array 的实例,但它认为 [ ] 也是 Object 的实例 why? - 从 `instanceof` 能够判断出 [ ].**proto** 指向 Array.prototype,而 Array.prototype.**proto** 又指向了 Object.prototype,最终 Object.prototype.**proto** 指向了 null,标志着原型链的结束。因此,[]、Array、Object 就在内部形成了一条原型链: ```js // 内部执行过程 function instanceof (A,B) = { var L = A.__proto__; var R = B.prototype; if(L === R) { // A的内部属性 __proto__ 指向 B 的原型对象 return true; } return false; } ``` constructor 使用技巧 ![constructor](/images/js/2018_11_02/constructor.png) toString ```js Object.prototype.toString.call(''); // [object String] Object.prototype.toString.call(1); // [object Number] Object.prototype.toString.call(true); // [object Boolean] Object.prototype.toString.call(Symbol()); //[object Symbol] Object.prototype.toString.call(undefined); // [object Undefined] Object.prototype.toString.call(null); // [object Null] Object.prototype.toString.call(new Function()); // [object Function] Object.prototype.toString.call(new Date()); // [object Date] Object.prototype.toString.call([]); // [object Array] Object.prototype.toString.call(new RegExp()); // [object RegExp] Object.prototype.toString.call(new Error()); // [object Error] Object.prototype.toString.call(document); // [object HTMLDocument] Object.prototype.toString.call(window); //[object global] window 是全局对象 global 的引用 ``` ### 代码风格 ```js let time = { a: function(a) { console.log(a); }, b: function(b) { console.log('b:' + b); }, }; time.b(385); ``` ### es6 常见代码片段 ```js // 获取对象的key Object.keys({ name: 'seamong', age: 1 }); // 获取对象里数据的数量 Object.keys({ name: 'seamong', age: 1 }).length; // 遍历数组 Object.entries({ name: 'seamong', age: 1 }); // extend功能 const obj = { name: 'seamong', age: 3 }; const newObj = { ...obj, job: 'IT', age: 18 }; console.log(newObj); // 获取列表的头和尾 const [head, ...tail] = [1, 2, 3]; const [head, ...initial] = [1, 2, 3].reverse(); ``` ## HTML DOM Document 对象 #### 给文档对象添加/移除事件句柄 ```js let btnEnter = document.getElementById('btnEnter'); function btn() { alert('单击事件,被触发'); } ``` ```js /* # addEventListener: 给文档对象添加事件句柄 # 语法: document.addEventListener(event,function,useCapture) # event: 必需。描述事件名称的字符串。 # function: 必需。描述了事件触发后执行的函数。 # useCapture: 可选: # true - 事件句柄在捕获阶段执行 # false- 默认。事件句柄在冒泡阶段执行 */ btnEnter.addEventListener('click', btn()); /*您可以通过函数名来引用外部函数:*/ document.addEventListener('click', myFunction); function myFunction() { document.getElementById('demo').innerHTML = 'Hello World'; } ``` ```js /* # reamveEventListener: 移除文档中的事件句柄 # 语法: document.remaveEventListener(event,function,useCapture) # event: 必需。要移除的事件名称。 # function: 必需。指定要移除的函数。 # useCapture: 可选: # true - 事件句柄在捕获阶段移除 # false- 默认。事件句柄在冒泡阶段移除 */ // removeEventListener // 移除文档对象已添加的事件句柄 btnEnter.removeEventListener('click', btn()); ``` #### 返回当前获取焦点元素 语法: doucment.activeElement.tagName
定义和使用:
activeElement 属性返回文档中当前获得焦点的元素。 ```html
``` ```js let name = document.getElementById('name'); let btnName = document.getElementById('btnName'); let btns = document.getElementById('btns'); ``` 用法一(获取子元素焦点事件名称): ```js btnS.addEventListener('click', function() { console.log(document.activeElement.tagName); // 输出所有子元素的焦点事件名称 }); ``` 用法二(元素本身的焦点事件名称): ```js name.addEventListener('click', function() { console.log(document.activeElement.tagName); }); ``` #### 添加 class 属性 ```js // 添加class属性 pater1.setAttribute('class', 'redtext'); // 获取class属性 :只对 ID 有效,class无效 let href = document.getElementById('href'); console.log(href.getAttribute('href')); ``` ## Array ### 常见代码片段 ```js // 判断数组 const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const obj = { name: 'hello', age: 1 }; // instanceof方法(推荐使用) console.log(arr instanceof Array); //true console.log(arr instanceof Object); //true,在数组的原型链上也能找到Object构造函数 console.log(obj instanceof Array); //false // constructor方法(不推荐:constructor方法可以被修改) const a = []; const b = {}; const c = /^[0-9]$/; console.log(a.constructor == Array); //true console.log(b.constructor == Array); //false console.log(c.constructor == Array); //false // 不推荐理由 const a = []; //作死将constructor属性改成了别的 a.contrtuctor = Object; console.log(a.constructor == Array); //false (哭脸) console.log(a.constructor == Object); //true (哭脸) console.log(a instanceof Array); //true (instanceof火眼金睛) // 用Object的toString方法 const a = ['Hello', 'Howard']; const b = { 0: 'Hello', 1: 'Howard' }; const c = 'Hello Howard'; console.log(a.toString()); //"Hello,Howard" console.log(b.toString()); //"[object Object]" console.log(c.toString()); //"Hello,Howard" // 通过改变toString执行时的上下文判断是否为数组 // call | apply Object.prototype.toString.call(a); // [object Array] Object.prototype.toString.call(b); // [object Object] Object.prototype.toString.call(c); // [object String] // or Object.prototype.toString.apply(a); //"[object Array]" Object.prototype.toString.apply(b); //"[object Object]" Object.prototype.toString.apply(c); //"[object String]" // 合并上述过程为函数 const isArray = (something) => { return Object.prototype.toString.call(something) === '[object Array]'; }; const a = []; const b = {}; isArray(a); //true isArray(b); //false // 用Array对象的isArray方法判断 const a = []; const b = {}; Array.isArray(a); //true Array.isArray(b); //false Object.prototype.toString = () => { console.log('Hello Howard'); }; const a = []; Array.isArray(a); //true // 兼容性写法 if (!Array.isArray) { Array.isArray = function(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; } // jquery方法判断 // 待续 ``` ### es6 常见代码片段 ```js // 遍历数组 [1, 2, 3].forEach(function(value, index) { console.log(value); }); // 映射新数组 arr = [1, 2, 3].map((v) => v * 2); // 所有元素是否通过测试 [1, 2, , 3, 4].every((v) => v > 3); // 是否有元素通过测试 [1, 2, , 3, 4].some((v) => v > 3); // 过滤数组 [1, 2, 3, 4, 5].filter((v) => v > 3); // 查找符合条件的元素: arr = [{ name: 'dashing', age: 18 }, { name: 'rmos', age: 1 }]; // 查找索引 [1, 2, 3].indexOf(2); // 连接数组 arr1 = [1, 2, 3]; arr2 = [4, 5, 6]; [...arr1, ...arr2]; //数组去重 arr = [1, 2, 3, 4, 3, 2, 1]; [...new Set(arr)]; ``` ## DOM ## 闭包 ### 下面是一个简单的闭包 ```js function foo(x) { var tmp = 3; function bar(y) { alert(x + y + ++tmp); } bar(10); } foo(2); ``` 概念:
闭包就是能够读取其他函数内部变量的函数。也可以理解成:定义再一个函数内部的函数。 作用:
理解函数内部与外部的桥梁 用途:
1.可以读取函数内部的变量。
2.让这些变量始终保持在内存中。 ### 实例+分析 #### 分析 简单来说 `getNameFunc()` 是一个闭包函数,
我们在 对象 `Objec`t 中,定义了它。
接着定义了 `var that=this`;
并且在 `getNameFunc()` 里面,`return` 一个匿名函数,
return 返回 `that.name` 和 `this.name`;
这个时候发现 `that.name ==> "My Object"`;
为什么这个时候 `this.name` 的值是 `"My Object"` 而不是`"The Window"`。
` 这个时候,表示的是内部对象 ` 中定义的 `name` 的值。
这个 `name` 的值,初始是 `"My Object"` ,但是在 `Object.getNameFunc()` 运行之后,
`this` 就不在表示 `Object` ,而是指代全局了。
那么这样子的话.不论我们如何去定义 `this.name` 的值。
全局中,`var name = "The Window"`; 被定义了。
我们这个时候 去输出 `this.name` 的话.结果都是 `"The Window"`。
那么为什么在 `Object.getNameFunc()` 函数运作之后,`this` 就表示全局了呢?
`getNameFunc()`是一个闭包函数。当我们运行它的时候。它被赋予全局变量
它就被存入到内存中。 ```js var name = 'The Window'; var object = { name: 'My Object', getNameFunc: function() { var that = this; return function() { return 'that:' + that.name + ';this:' + this.name; }; }, }; console.log(object.getNameFunc()()); //that:My Object;this:The Window ``` 从技术上来讲,在 JS 中,每个 function 都是闭包,因为它总是能访问在它外部定义的数据。 ## 写给初级 JS 程序员的 JavaScript 闭包(译) [原文链接](http://web.archive.org/web/20080209105120/http:/blog.morrisjohns.com/javascript_closures_for_dummies) ### 闭包不是魔术 这个页面解释了 closures,以便程序员可以理解它们 - 使用有效的 JavaScript 代码。它不是用于古茹或功能程序员。
一旦核心概念被解构,闭包不难理解。然而,他们不可能通过阅读任何学术论文或面向学术的信息来了解他们!
本文主要面向有主流语言编程经验的程序员,他们可以阅读以下 JavaScript 函数: ```js function sayHello(name) { var text = 'Hello ' + name; var say = function() { console.log(text); }; say(); } sayHello('Joe'); ``` ### 闭包的实例 两句话摘要:
它是一个可以引用其范围内的变量(在第一次声明时),被赋值给变量,作为参数传递给函数或作为函数结果返回的表达式。
闭包是一个堆栈框架,当函数开始执行时被分配,并且在函数返回之后不被释放(就好像“堆栈框架”在堆上分配而不是堆栈!)。 以下代码返回对函数的引用: ```js function sayHello2(name) { var text = 'Hello ' + name; // Local variable var say = function() { console.log(text); }; return say; } var say2 = sayHello2('Bob'); say2(); // logs "Hello Bob" ``` 大多数 JavaScript 程序员将会理解在上面的代码中如何将一个函数的引用返回给一个变量。如果你没有,那么你需要在你可以学习闭包。AC 程序员会认为函数返回一个指向函数的指针,变量 sayAlert 和 say2 都是指向函数的指针。 在指向函数的 C 指针和对函数的 JavaScript 引用之间存在关键的区别。在 JavaScript 中,您可以将函数引用变量看作具有指向函数的指针 以及指向闭包的隐藏指针。 上面的代码有一个闭包,因为匿名函数 function(){alert(text); } 在另一个函数中声明,在这个例子中为 sayHello2()。在 JavaScript 中,如果在另一个函数中使用 function 关键字,则要创建一个闭包。 在 C 和大多数其他常用语言函数返回后,所有的局部变量不再可访问,因为堆栈帧被销毁。 在 JavaScript 中,如果在另一个函数中声明一个函数,那么在从调用的函数返回后,局部变量仍然可以访问。这是上面演示的,因为我们调用函数 say2(); 之后我们从 sayHello2()返回。请注意,我们调用的代码引用变量 text,它是函数 sayHello2()的局部变量。 ```js function() { console.log(text); } // Output of say2.toString(); ``` 看看输出 say2.toString(),我们可以看到代码引用的变量 text。匿名函数可以引用 text 保存值的值'Hello Bob',因为局部变量 sayHello2()保存在闭包中。 神奇的是,在 JavaScript 中,函数引用还具有对其创建的闭包的秘密引用 - 类似于如何委托是方法指针加上对对象的秘密引用。 --- ### 更多例子 出于某种原因,当你阅读关于它们的闭包似乎真的很难理解,但当你看到一些例子,你可以点击他们的工作(它花了我一段时间)。我建议仔细阅读示例,直到你了解它们如何工作。如果你开始使用闭包没有完全了解它们如何工作,你很快就会创建一些非常古怪的错误! #### 例 3 此示例显示不复制局部变量 - 它们通过引用保留。它是一种像在外部函数退出时在内存中保留一个堆栈框架! ```js function say667() { // Local variable that ends up within closure var num = 42; var say = function() { console.log(num); }; num++; return say; } var sayNumber = say667(); sayNumber(); // logs 43 ``` #### 例 4 所有三个全局函数具有对同一闭包的公共引用,因为它们都在单个调用中声明 setupSomeGlobals()。
```js var gLogNumber, gIncreaseNumber, gSetNumber; function setupSomeGlobals() { // Local variable that ends up within closure var num = 42; // Store some references to functions as global variables gLogNumber = function() { console.log(num); }; gIncreaseNumber = function() { num++; }; gSetNumber = function(x) { num = x; }; } setupSomeGlobals(); gIncreaseNumber(); gLogNumber(); // 43 gSetNumber(5); gLogNumber(); // 5 var oldLog = gLogNumber; setupSomeGlobals(); gLogNumber(); // 42 oldLog(); // 5 ``` 这三个函数具有对同一闭包的共享访问 - setupSomeGlobals()当定义三个函数时的局部变量。
注意,在上面的例子中,如果 setupSomeGlobals()再次调用,则创建一个新的闭包(stack-frame!)。老 gLogNumber,gIncreaseNumber,gSetNumber 变量将覆盖新的具有新功能关闭。(在 JavaScript 中,当你声明另一个函数内的函数,里面的功能(s)是/再次重新创建每个外部函数被调用时)。 #### 例 5 这是一个真正的困扰,许多人,所以你需要了解它。要非常小心,如果你在一个循环中定义一个函数:从闭包的局部变量不会像你可能会想的那样。 ```js function buildList(list) { var result = []; for (var i = 0; i < list.length; i++) { var item = 'item' + i; result.push(function() { console.log(item + ' ' + list[i]); }); } return result; } function testList() { var fnlist = buildList([1, 2, 3]); // Using j only to help prevent confusion -- could use i. for (var j = 0; j < fnlist.length; j++) { fnlist[j](); } } testList(); //logs "item2 undefined" 3 times ``` 在结果数组中添加一个对匿名函数的引用三次。如果你不熟悉匿名函数, 就如: ```js pointer = function() { console.log(item + ' ' + list[i]); }; result.push(pointer); ``` 注意,当运行示例时,“item2 undefined”会提醒三次! 这是因为就像前面的例子一样,buildList 的局部变量只有一个闭包。 当在 fnlist [j]()上调用匿名函数时; 它们都使用相同的单个闭包,并且它们使用该闭包内的 i 和项的当前值(其中 i 具有值 3,因为循环已完成,并且项具有值'item2')。 注意,我们从 0 索引,因此项目的值为 item2。 并且 i ++将 i 增加到值 3。 #### 例 6 此示例显示,闭包包含在退出前在外部函数中声明的任何局部变量。 注意,变量 alice 实际上是在匿名函数之后声明的。 匿名函数首先声明; 并且当调用该函数时,它可以访问 alice 变量,因为 alice 在同一范围内(JavaScript 变量提升)。 另外 sayAlice()()只是直接调用从 sayAlice()返回的函数引用 - 它完全与以前做过的相同,但没有临时变量。 ```js function sayAlice() { var say = function() { console.log(alice); }; // Local variable that ends up within closure var alice = 'Hello Alice'; return say; } sayAlice()(); // logs "Hello Alice" ``` Tricky:还要注意,say 变量也在闭包内,并且可以被可能被声明的任何其他函数访问 sayAlice(),或者它可以在内部函数内被递归地访问。 #### 例 7 最后一个例子显示每个调用为局部变量创建一个单独的闭包。 每个函数声明没有单个闭包。 每个函数的调用都有一个闭包。 ```js function newClosure(someNum, someRef) { // Local variables that end up within closure var num = someNum; var anArray = [1, 2, 3]; var ref = someRef; return function(x) { num += x; anArray.push(num); console.log( 'num: ' + num + '; anArray: ' + anArray.toString() + '; ref.someVar: ' + ref.someVar + ';', ); }; } obj = { someVar: 4 }; fn1 = newClosure(4, obj); fn2 = newClosure(5, obj); fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4; fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4; obj.someVar++; fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5; fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5; ``` --- #### 概要 如果一切似乎完全不清楚,那么最好的办法是玩的例子。阅读解释比理解示例困难得多。我对闭包和堆栈框架等的解释在技术上是不正确的 - 它们是用于帮助理解的粗略简化。一旦基本的想法是 grokked,你可以提取细节后。 #### 总结: 1.当你 function 在另一个函数里面使用时,使用闭包。 2.每当在函数中使用 eval()时,都会使用闭包。 eval 的文本可以引用函数的局部变量,在 eval 中甚至可以使用 eval('var foo = ...')创建新的局部变量 3.当在函数中使用新的 Function(...)(Function 构造函数)时,它不会创建闭包。 (新函数不能引用外层函数的局部变量。) 4.JavaScript 中的闭包就像保留所有局部变量的副本,就像函数退出时一样。 5.最好是认为闭包始终只创建一个函数的入口,局部变量将添加到闭包中。 6.每次调用具有闭包的函数时,都会保存一组新的局部变量(假定函数在其中包含函数声明,并且对该函数的引用要么返回,要么以某种方式保留外部引用 )。 7.两个函数可能看起来像它们具有相同的源文本,但是具有完全不同的行为,因为它们的“隐藏”闭包。 我不认为 JavaScript 代码实际上可以找出一个函数引用是否有闭包。 8.如果你试图做任何动态源代码修改(例如:myFunction = Function(myFunction.toString()。replace(/ Hello /,'Hola'));),如果 myFunction 是一个闭包 当然,你永远不会想到在运行时做源代码字符串替换,但...)。 9.可以在函数内的函数声明中获得函数声明 - 你可以在多个级别获得闭包。 10.我认为通常闭包是函数和捕获的变量的术语。 注意,我在本文中不使用这个定义! 11.我怀疑 JavaScript 中的闭包不同于通常在函数式语言中发现的闭包。 #### 链接: 1.Douglas Crockford 的模拟[私有属性和一个对象的私有方法](http://www.crockford.com/javascript/private.html),使用闭包。 2.一个伟大的解释如何闭包可以导致[内存泄漏在 IE](https://www.codeproject.com/Articles/12231/Memory-Leakage-in-Internet-Explorer-revisited)如果你不小心。 #### 感谢 If you have just learnt closures (here or elsewhere!),
如果你刚刚学会了闭包(在这里或其他地方!), then I am interested in any feedback from you about any changes you might suggest that could make this article clearer.
那么我对你提出的任何有关你可能提出的任何可能使本文更清晰的变化的反馈感兴趣。 Send an email to morrisjohns.com (morris_closure @). Please note that I am not a guru on JavaScript - nor on closures. 发送电子邮件至morrisjohns.com(morris_closure @)。 请注意,我不是JavaScript的大师 - nor on closures.