做者:Mark A
译者:前端小智
来源:dev
点赞再看,微信搜索
【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文
GitHub
https://github.com/qq44924588... 上已经收录,文章的已分类,也整理了不少个人文档,和教程资料。
编程,建网站必备的阿里云服务器居然免费送了!javascript
服务器如何搭建博客html
Array.prototype.map
方法Array.prototype.filter
方法Array.prototype.reduce
方法b
会变成一个全局变量?在理解undefined
和null
之间的差别以前,咱们先来看看它们的类似类。前端
它们属于 JavaScript 的 7 种基本类型。java
let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];
它们是属于虚值,可使用Boolean(value)
或!!value
将其转换为布尔值时,值为false
。react
console.log(!!null); // false console.log(!!undefined); // false console.log(Boolean(null)); // false console.log(Boolean(undefined)); // false
接着来看看它们的区别。git
undefined
是未指定特定值的变量的默认值,或者没有显式返回值的函数,如:console.log(1)
,还包括对象中不存在的属性,这些 JS 引擎都会为其分配 undefined
值。github
let _thisIsUndefined; const doNothing = () => {}; const someObj = { a : "ay", b : "bee", c : "si" }; console.log(_thisIsUndefined); // undefined console.log(doNothing()); // undefined console.log(someObj["d"]); // undefined
null
是“不表明任何值的值”。 null
是已明肯定义给变量的值。 在此示例中,当fs.readFile
方法未引起错误时,咱们将得到null
值。面试
fs.readFile('path/to/file', (e,data) => { console.log(e); // 当没有错误发生时,打印 null if(e){ console.log(e); } console.log(data); });
在比较null
和undefined
时,咱们使用==
时获得true
,使用===
时获得false
:算法
console.log(null == undefined); // true console.log(null === undefined); // false
&&
也能够叫逻辑与,在其操做数中找到第一个虚值表达式并返回它,若是没有找到任何虚值表达式,则返回最后一个真值表达式。它采用短路来防止没必要要的工做。编程
console.log(false && 1 && []); // false console.log(" " && true && 5); // 5
使用if
语句
const router: Router = Router(); router.get('/endpoint', (req: Request, res: Response) => { let conMobile: PoolConnection; try { //do some db operations } catch (e) { if (conMobile) { conMobile.release(); } } });
使用&&
操做符
const router: Router = Router(); router.get('/endpoint', (req: Request, res: Response) => { let conMobile: PoolConnection; try { //do some db operations } catch (e) { conMobile && conMobile.release() } });
||
也叫或逻辑或
,在其操做数中找到第一个真值表达式并返回它。这也使用了短路来防止没必要要的工做。在支持 ES6 默认函数参数以前,它用于初始化函数中的默认参数值。
console.log(null || 1 || undefined); // 1 function logName(name) { var n = name || "Mark"; console.log(n); } logName(); // "Mark"
根据MDN文档,+
是将字符串转换为数字的最快方法,由于若是值已是数字,它不会执行任何操做。
DOM 表明文档对象模型,是 HTML 和 XML 文档的接口(API)。当浏览器第一次读取(解析)HTML文档时,它会建立一个大对象,一个基于 HTM L文档的很是大的对象,这就是DOM。它是一个从 HTML 文档中建模的树状结构。DOM 用于交互和修改DOM结构或特定元素或节点。
假设咱们有这样的 HTML 结构:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document Object Model</title> </head> <body> <div> <p> <span></span> </p> <label></label> <input> </div> </body> </html>
等价的DOM是这样的:
JS 中的document
对象表示DOM。它为咱们提供了许多方法,咱们可使用这些方法来选择元素来更新元素内容,等等。
当事件发生在DOM元素上时,该事件并不彻底发生在那个元素上。 在“冒泡阶段”中,事件冒泡或向上传播至父级,祖父母,祖父母或父级,直到到达window
为止;而在“捕获阶段”中,事件从window
开始向下触发元素 事件或event.target
。
事件传播有三个阶段:
window
开始,而后向下到每一个元素,直到到达目标元素。window
。当事件发生在DOM元素上时,该事件并不彻底发生在那个元素上。 在冒泡阶段,事件冒泡,或者事件发生在它的父代,祖父母,祖父母的父代,直到到达window
为止。
假设有以下的 HTML 结构:
<div class="grandparent"> <div class="parent"> <div class="child">1</div> </div> </div>
对应的 JS 代码:
function addEvent(el, event, callback, isCapture = false) { if (!el || !event || !callback || typeof callback !== 'function') return; if (typeof el === 'string') { el = document.querySelector(el); }; el.addEventListener(event, callback, isCapture); } addEvent(document, 'DOMContentLoaded', () => { const child = document.querySelector('.child'); const parent = document.querySelector('.parent'); const grandparent = document.querySelector('.grandparent'); addEvent(child, 'click', function (e) { console.log('child'); }); addEvent(parent, 'click', function (e) { console.log('parent'); }); addEvent(grandparent, 'click', function (e) { console.log('grandparent'); }); addEvent(document, 'click', function (e) { console.log('document'); }); addEvent('html', 'click', function (e) { console.log('html'); }) addEvent(window, 'click', function (e) { console.log('window'); }) });
addEventListener
方法具备第三个可选参数useCapture
,其默认值为false
,事件将在冒泡阶段中发生,若是为true
,则事件将在捕获阶段中发生。 若是单击child
元素,它将分别在控制台上记录child
,parent
,grandparent
,html
,document
和window
,这就是事件冒泡。
当事件发生在 DOM 元素上时,该事件并不彻底发生在那个元素上。在捕获阶段,事件从window
开始,一直到触发事件的元素。
假设有以下的 HTML 结构:
<div class="grandparent"> <div class="parent"> <div class="child">1</div> </div> </div>
对应的 JS 代码:
function addEvent(el, event, callback, isCapture = false) { if (!el || !event || !callback || typeof callback !== 'function') return; if (typeof el === 'string') { el = document.querySelector(el); }; el.addEventListener(event, callback, isCapture); } addEvent(document, 'DOMContentLoaded', () => { const child = document.querySelector('.child'); const parent = document.querySelector('.parent'); const grandparent = document.querySelector('.grandparent'); addEvent(child, 'click', function (e) { console.log('child'); }); addEvent(parent, 'click', function (e) { console.log('parent'); }); addEvent(grandparent, 'click', function (e) { console.log('grandparent'); }); addEvent(document, 'click', function (e) { console.log('document'); }); addEvent('html', 'click', function (e) { console.log('html'); }) addEvent(window, 'click', function (e) { console.log('window'); }) });
addEventListener
方法具备第三个可选参数useCapture
,其默认值为false
,事件将在冒泡阶段中发生,若是为true
,则事件将在捕获阶段中发生。 若是单击child
元素,它将分别在控制台上打印window
,document
,html
,grandparent
和parent
,这就是事件捕获。
event.preventDefault()
方法可防止元素的默认行为。 若是在表单元素中使用,它将阻止其提交。 若是在锚元素中使用,它将阻止其导航。 若是在上下文菜单中使用,它将阻止其显示或显示。 event.stopPropagation()
方法用于阻止捕获和冒泡阶段中当前事件的进一步传播。
event.preventDefault()
方法?咱们能够在事件对象中使用event.defaultPrevented
属性。 它返回一个布尔值用来代表是否在特定元素中调用了event.preventDefault()
。
obj.someprop.x
会引起错误?const obj = {}; console.log(obj.someprop.x);
显然,因为咱们尝试访问someprop
属性中的x
属性,而 someprop 并无在对象中,因此值为 undefined
。 记住对象自己不存在的属性,而且其原型的默认值为undefined
。由于undefined
没有属性x
,因此试图访问将会报错。
简单来讲,event.target
是发生事件的元素或触发事件的元素。
假设有以下的 HTML 结构:
<div onclick="clickFunc(event)" style="text-align: center;margin:15px; border:1px solid red;border-radius:3px;"> <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;"> <div style="margin:25px;border:1px solid skyblue;border-radius:3px;"> <button style="margin:10px"> Button </button> </div> </div> </div>
JS 代码以下:
function clickFunc(event) { console.log(event.target); }
若是单击 button
,即便咱们将事件附加在最外面的div
上,它也将打印 button
标签,所以咱们能够得出结论event.target
是触发事件的元素。
event.currentTarget
是咱们在其上显式附加事件处理程序的元素。
假设有以下的 HTML 结构:
<div onclick="clickFunc(event)" style="text-align: center;margin:15px; border:1px solid red;border-radius:3px;"> <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;"> <div style="margin:25px;border:1px solid skyblue;border-radius:3px;"> <button style="margin:10px"> Button </button> </div> </div> </div>
JS 代码以下:
function clickFunc(event) { console.log(event.currentTarget); }
若是单击 button
,即便咱们单击该 button
,它也会打印最外面的div
标签。 在此示例中,咱们能够得出结论,event.currentTarget
是附加事件处理程序的元素。
==
用于通常比较,===
用于严格比较,==
在比较的时候能够转换数据类型,===
严格比较,只要类型不匹配就返回flase
。
先来看看 ==
这兄弟:
强制是将值转换为另外一种类型的过程。 在这种状况下,==
会执行隐式强制。 在比较两个值以前,==
须要执行一些规则。
假设咱们要比较x == y
的值。
x
和y
的类型相同,则 JS 会换成===
操做符进行比较。x
为null
, y
为undefined
,则返回true
。x
为undefined
且y
为null
,则返回true
。x
的类型是number
, y
的类型是string
,那么返回x == toNumber(y)
。x
的类型是string
, y
的类型是number
,那么返回toNumber(x) == y
。x
为类型是boolean
,则返回toNumber(x)== y
。y
为类型是boolean
,则返回x == toNumber(y)
。x
是string
、symbol
或number
,而y
是object
类型,则返回x == toPrimitive(y)
。x
是object
,y
是string
,symbol
则返回toPrimitive(x) == y
。false
注意:toPrimitive
首先在对象中使用valueOf
方法,而后使用toString
方法来获取该对象的原始值。
举个例子。
x | y | x == y |
---|---|---|
5 | 5 | true |
1 | '1' | true |
null | undefined | true |
0 | false | true |
'1,2' | [1,2] | true |
'[object Object]' | {} | true |
这些例子都返回true
。
第一个示例符合条件1
,由于x
和y
具备相同的类型和值。
第二个示例符合条件4
,在比较以前将y
转换为数字。
第三个例子符合条件2
。
第四个例子符合条件7
,由于y
是boolean
类型。
第五个示例符合条件8
。 使用toString()
方法将数组转换为字符串,该方法返回1,2
。
最后一个示例符合条件8
。 使用toString()
方法将对象转换为字符串,该方法返回[object Object]
。
x | y | x === y |
---|---|---|
5 | 5 | true |
1 | '1' | false |
null | undefined | false |
0 | false | false |
'1,2' | [1,2] | false |
'[object Object]' | {} | false |
若是使用===
运算符,则第一个示例之外的全部比较将返回false
,由于它们的类型不一样,而第一个示例将返回true
,由于二者的类型和值相同。
具体更多规则能够对参考我以前的文章:
我对 JS 中相等和全等操做符转化过程一直很迷惑,直到有了这份算法
先看下面的例子:
let a = { a: 1 }; let b = { a: 1 }; let c = a; console.log(a === b); // 打印 false,即便它们有相同的属性 console.log(a === c); // true
JS 以不一样的方式比较对象和基本类型。在基本类型中,JS 经过值对它们进行比较,而在对象中,JS 经过引用或存储变量的内存中的地址对它们进行比较。这就是为何第一个console.log
语句返回false
,而第二个console.log
语句返回true
。a
和c
有相同的引用地址,而a
和b
没有。
!!
运算符能够将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简单方法。
console.log(!!null); // false console.log(!!undefined); // false console.log(!!''); // false console.log(!!0); // false console.log(!!NaN); // false console.log(!!' '); // true console.log(!!{}); // true console.log(!![]); // true console.log(!!1); // true console.log(!![].length); // false
可使用逗号
运算符在一行中计算多个表达式。 它从左到右求值,并返回右边最后一个项目或最后一个操做数的值。
let x = 5; x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10); function addFive(num) { return num + 5; }
上面的结果最后获得x
的值为27
。首先,咱们将x
的值增长到6
,而后调用函数addFive(6)
并将6
做为参数传递并将结果从新分配给x
,此时x
的值为11
。以后,将x
的当前值乘以2
并将其分配给x
,x
的更新值为22
。而后,将x
的当前值减去5
并将结果分配给x
x
更新后的值为17
。最后,咱们将x
的值增长10
,而后将更新的值分配给x
,最终x
的值为27
。
提高是用来描述变量和函数移动到其(全局或函数)做用域顶部的术语。
为了理解提高,须要来了解一下执行上下文。执行上下文是当前正在执行的“代码环境”。执行上下文有两个阶段:编译
和执行
。
编译-在此阶段,JS 引荐获取全部函数声明并将其提高到其做用域的顶部,以便咱们稍后能够引用它们并获取全部变量声明(使用var
关键字进行声明),还会为它们提供默认值: undefined
。
执行——在这个阶段中,它将值赋给以前提高的变量,并执行或调用函数(对象中的方法)。
注意:只有使用var
声明的变量,或者函数声明才会被提高,相反,函数表达式或箭头函数,let
和const
声明的变量,这些都不会被提高。
假设在全局使用域,有以下的代码:
console.log(y); y = 1; console.log(y); console.log(greet("Mark")); function greet(name){ return 'Hello ' + name + '!'; } var y;
上面分别打印:undefined
,1
, Hello Mark!
。
上面代码在编译阶段实际上是这样的:
function greet(name) { return 'Hello ' + name + '!'; } var y; // 默认值 undefined // 等待“编译”阶段完成,而后开始“执行”阶段 /* console.log(y); y = 1; console.log(y); console.log(greet("Mark")); */
编译阶段完成后,它将启动执行阶段调用方法,并将值分配给变量。
function greet(name) { return 'Hello ' + name + '!'; } var y; //start "execution" phase console.log(y); y = 1; console.log(y); console.log(greet("Mark"));
JavaScript 中的做用域是咱们能够有效访问变量或函数的区域。JS 有三种类型的做用域:全局做用域、函数做用域和块做用域(ES6)。
//global namespace var g = "global"; function globalFunc(){ function innerFunc(){ console.log(g); // can access "g" because "g" is a global variable } innerFunc(); }
function myFavoriteFunc(a) { if (true) { var b = "Hello " + a; } return b; } myFavoriteFunc("World"); console.log(a); // Throws a ReferenceError "a" is not defined console.log(b); // does not continue here
{}
中声明的变量(let,const
)只能在其中访问。function testBlock(){ if(true){ let z = 5; } return z; } testBlock(); // Throws a ReferenceError "z" is not defined
做用域也是一组用于查找变量的规则。 若是变量在当前做用域中不存在,它将向外部做用域中查找并搜索,若是该变量不存在,它将再次查找直到到达全局做用域,若是找到,则可使用它,不然引起错误,这种查找过程也称为做用域链。
/* 做用域链 内部做用域->外部做用域-> 全局做用域 */ // 全局做用域 var variable1 = "Comrades"; var variable2 = "Sayonara"; function outer(){ // 外部做用域 var variable1 = "World"; function inner(){ // 内部做用域 var variable2 = "Hello"; console.log(variable2 + " " + variable1); } inner(); } outer(); // Hello World
这多是全部问题中最难的一个问题,由于闭包是一个有争议的话题,这里从我的角度来谈谈,若是不妥,多多海涵。
闭包就是一个函数在声明时可以记住当前做用域、父函数做用域、及父函数做用域上的变量和参数的引用,直至经过做用域链上全局做用域,基本上闭包是在声明函数时建立的做用域。
看看小例子:
// 全局做用域 var globalVar = "abc"; function a(){ console.log(globalVar); } a(); // "abc"
在此示例中,当咱们声明a
函数时,全局做用域是a
闭包的一部分。
变量globalVar
在图中没有值的缘由是该变量的值能够根据调用函数a
的位置和时间而改变。可是在上面的示例中,globalVar
变量的值为abc
。
来看一个更复杂的例子:
var globalVar = "global"; var outerVar = "outer" function outerFunc(outerParam) { function innerFunc(innerParam) { console.log(globalVar, outerParam, innerParam); } return innerFunc; } const x = outerFunc(outerVar); outerVar = "outer-2"; globalVar = "guess" x("inner");
上面打印结果是 guess outer inner
。
当咱们调用outerFunc
函数并将返回值innerFunc
函数分配给变量x
时,即便咱们为outerVar
变量分配了新值outer-2
,outerParam
也继续保留outer
值,由于从新分配是在调用outerFunc
以后发生的,而且当咱们调用outerFunc
函数时,它会在做用域链中查找outerVar
的值,此时的outerVar
的值将为 "outer"
。
如今,当咱们调用引用了innerFunc
的x
变量时,innerParam
将具备一个inner
值,由于这是咱们在调用中传递的值,而globalVar
变量值为guess
,由于在调用x
变量以前,咱们将一个新值分配给globalVar
。
下面这个示例演示没有理解好闭包所犯的错误:
const arrFuncs = []; for(var i = 0; i < 5; i++){ arrFuncs.push(function (){ return i; }); } console.log(i); // i is 5 for (let i = 0; i < arrFuncs.length; i++) { console.log(arrFuncs[i]()); // 都打印 5 }
因为闭包,此代码没法正常运行。var
关键字建立一个全局变量,当咱们 push 一个函数时,这里返回的全局变量i
。 所以,当咱们在循环后在该数组中调用其中一个函数时,它会打印5
,由于咱们获得i
的当前值为5
,咱们能够访问它,由于它是全局变量。
由于闭包在建立变量时会保留该变量的引用而不是其值。 咱们可使用IIFES或使用 let
来代替 var
的声明。
const falsyValues = ['', 0, null, undefined, NaN, false];
简单的来讲虚值就是是在转换为布尔值时变为 false
的值。
使用 Boolean
函数或者 !!
运算符。
"use strict"
是 ES5 特性,它使咱们的代码在函数或整个脚本中处于严格模式。严格模式帮助咱们在代码的早期避免 bug,并为其添加限制。
严格模式的一些限制:
with
语句delete prop
,会报错,只能删除属性delete global[prop]
eval
不能在它的外层做用域引入变量eval
和arguments
不能被从新赋值arguments
不会自动反映函数参数的变化arguments.callee
arguments.caller
this
指向全局对象fn.caller
和fn.arguments
获取函数调用的堆栈protected
、static
和interface
)设立”严格模式”的目的,主要有如下几个:
this
值是什么?基本上,this
指的是当前正在执行或调用该函数的对象的值。this
值的变化取决于咱们使用它的上下文和咱们在哪里使用它。
const carDetails = { name: "Ford Mustang", yearBought: 2005, getName(){ return this.name; }, isRegistered: true }; console.log(carDetails.getName()); // Ford Mustang
这一般是咱们指望结果的,由于在getName
方法中咱们返回this.name
,在此上下文中,this
指向的是carDetails
对象,该对象当前是执行函数的“全部者”对象。
接下咱们作些奇怪的事情:
var name = "Ford Ranger"; var getCarName = carDetails.getName; console.log(getCarName()); // Ford Ranger
上面打印Ford Ranger
,这很奇怪,由于在第一个console.log
语句中打印的是Ford Mustang
。这样作的缘由是getCarName
方法有一个不一样的“全部者”对象,即window
对象。在全局做用域中使用var
关键字声明变量会在window
对象中附加与变量名称相同的属性。请记住,当没有使用“use strict”
时,在全局做用域中this
指的是window
对象。
console.log(getCarName === window.getCarName); // true console.log(getCarName === this.getCarName); // true
本例中的this
和window
引用同一个对象。
解决这个问题的一种方法是在函数中使用apply
和call
方法。
console.log(getCarName.apply(carDetails)); // Ford Mustang console.log(getCarName.call(carDetails)); // Ford Mustang
apply
和call
方法指望第一个参数是一个对象,该对象是函数内部this
的值。
IIFE
或当即执行的函数表达式,在全局做用域内声明的函数,对象内部方法中的匿名函数和内部函数的this
具备默认值,该值指向window
对象。
(function (){ console.log(this); })(); // 打印 "window" 对象 function iHateThis(){ console.log(this); } iHateThis(); // 打印 "window" 对象 const myFavoriteObj = { guessThis(){ function getName(){ console.log(this.name); } getName(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } }; myFavoriteObj.guessThis(); // 打印 "window" 对象 myFavoriteObj.thisIsAnnoying(function (){ console.log(this); // 打印 "window" 对象 });
若是咱们要获取myFavoriteObj
对象中的name
属性(即Marko Polo)的值,则有两种方法能够解决此问题。
一种是将 this
值保存在变量中。
const myFavoriteObj = { guessThis(){ const self = this; // 把 this 值保存在 self 变量中 function getName(){ console.log(self.name); } getName(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } };
第二种方式是使用箭头函数
const myFavoriteObj = { guessThis(){ const getName = () => { console.log(this.name); } getName(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } };
箭头函数没有本身的 this
。它复制了这个封闭的词法做用域中this
值,在这个例子中,this
值在getName
内部函数以外,也就是myFavoriteObj
对象。
简单地说,原型就是对象的蓝图。若是它存在当前对象中,则将其用做属性和方法的回退。它是在对象之间共享属性和功能的方法,这也是JavaScript实现继承的核心。
const o = {}; console.log(o.toString()); // logs [object Object]
即便o
对象中不存在o.toString
方法,它也不会引起错误,而是返回字符串[object Object]
。 当对象中不存在属性时,它将查看其原型,若是仍然不存在,则将其查找到原型的原型,依此类推,直到在原型链中找到具备相同属性的属性为止。 原型链的末尾是Object.prototype
。
console.log(o.toString === Object.prototype.toString); // logs true
IIFE或当即调用的函数表达式是在建立或声明后将被调用或执行的函数。 建立IIFE的语法是,将function (){}
包裹在在括号()
内,而后再用另外一个括号()
调用它,如:(function(){})()
(function(){ ... } ()); (function () { ... })(); (function named(params) { ... })(); (() => { }); (function (global) { ... })(window); const utility = (function () { return { ... } })
这些示例都是有效的IIFE。 倒数第二个救命代表咱们能够将参数传递给IIFE函数。 最后一个示例代表,咱们能够将IIFE
的结果保存到变量中,以便稍后使用。
IIFE的一个主要做用是避免与全局做用域内的其余变量命名冲突或污染全局命名空间,来个例子。
<script src="https://cdnurl.com/somelibrary.js"></script>
假设咱们引入了一个omelibr.js
的连接,它提供了一些咱们在代码中使用的全局函数,可是这个库有两个方法咱们没有使用:createGraph
和drawGraph
,由于这些方法都有bug
。咱们想实现本身的createGraph
和drawGraph
方法。
解决此问题的一种方法是直接覆盖:
<script src="https://cdnurl.com/somelibrary.js"></script> <script> function createGraph() { // createGraph logic here } function drawGraph() { // drawGraph logic here } </script>
当咱们使用这个解决方案时,咱们覆盖了库提供给咱们的那两个方法。
另外一种方式是咱们本身更名称:
<script src="https://cdnurl.com/somelibrary.js"></script> <script> function myCreateGraph() { // createGraph logic here } function myDrawGraph() { // drawGraph logic here } </script>
当咱们使用这个解决方案时,咱们把那些函数调用更改成新的函数名。
还有一种方法就是使用IIFE:
<script src="https://cdnurl.com/somelibrary.js"></script> <script> const graphUtility = (function () { function createGraph() { // createGraph logic here } function drawGraph() { // drawGraph logic here } return { createGraph, drawGraph } }) </script>
在此解决方案中,咱们要声明了graphUtility
变量,用来保存IIFE执行的结果,该函数返回一个包含两个方法createGraph
和drawGraph
的对象。
IIFE 还能够用来解决一个常见的面试题:
var li = document.querySelectorAll('.list-group > li'); for (var i = 0, len = li.length; i < len; i++) { li[i].addEventListener('click', function (e) { console.log(i); })
假设咱们有一个带有list-group
类的ul
元素,它有5
个li
子元素。 当咱们单击单个li
元素时,打印对应的下标值。但在此外上述代码不起做用,这里每次点击 li
打印 i
的值都是5
,这是因为闭包的缘由。
闭包只是函数记住其当前做用域,父函数做用域和全局做用域的变量引用的能力。 当咱们在全局做用域内使用var
关键字声明变量时,就建立全局变量i
。 所以,当咱们单击li
元素时,它将打印5
,由于这是稍后在回调函数中引用它时i
的值。
使用 IIFE 能够解决此问题:
var li = document.querySelectorAll('.list-group > li'); for (var i = 0, len = li.length; i < len; i++) { (function (currentIndex) { li[currentIndex].addEventListener('click', function (e) { console.log(currentIndex); }) })(i); }
该解决方案之因此行的通,是由于IIFE会为每次迭代建立一个新的做用域,咱们捕获i
的值并将其传递给currentIndex
参数,所以调用IIFE时,每次迭代的currentIndex
值都是不一样的。
apply()
方法调用一个具备给定this值的函数,以及做为一个数组(或相似数组对象)提供的参数。
const details = { message: 'Hello World!' }; function getMessage(){ return this.message; } getMessage.apply(details); // 'Hello World!'
call()
方法的做用和apply()
方法相似,区别就是call()
方法接受的是参数列表,而apply()
方法接受的是一个参数数组。
const person = { name: "Marko Polo" }; function greeting(greetingMessage) { return `${greetingMessage} ${this.name}`; } greeting.apply(person, ['Hello']); // "Hello Marko Polo!"
Function.prototype.call
方法的用途是什么?call()
方法使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数。
const details = { message: 'Hello World!' }; function getMessage(){ return this.message; } getMessage.call(details); // 'Hello World!'
注意:该方法的语法和做用与 apply()
方法相似,只有一个区别,就是 call()
方法接受的是一个参数列表,而 apply()
方法接受的是一个包含多个参数的数组。
const person = { name: "Marko Polo" }; function greeting(greetingMessage) { return `${greetingMessage} ${this.name}`; } greeting.call(person, 'Hello'); // "Hello Marko Polo!"
apply()
方法能够在使用一个指定的 this
值和一个参数数组(或类数组对象)的前提下调用某个函数或方法。call()
方法相似于apply()
,不一样之处仅仅是call()
接受的参数是参数列表。
const obj1 = { result:0 }; const obj2 = { result:0 }; function reduceAdd(){ let result = 0; for(let i = 0, len = arguments.length; i < len; i++){ result += arguments[i]; } this.result = result; } reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // 15 reduceAdd.call(obj2, 1, 2, 3, 4, 5); // 15
bind()
方法建立一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其他参数将做为新函数的参数,供调用时使用。
import React from 'react'; class MyComponent extends React.Component { constructor(props){ super(props); this.state = { value : "" } this.handleChange = this.handleChange.bind(this); // 将 “handleChange” 方法绑定到 “MyComponent” 组件 } handleChange(e){ //do something amazing here } render(){ return ( <> <input type={this.props.type} value={this.state.value} onChange={this.handleChange} /> </> ) } }
函数式编程(一般缩写为FP)是经过编写纯函数,避免共享状态、可变数据、反作用 来构建软件的过程。数式编程是声明式 的而不是命令式 的,应用程序的状态是经过纯函数流动的。与面向对象编程造成对比,面向对象中应用程序的状态一般与对象中的方法共享和共处。
函数式编程是一种编程范式 ,这意味着它是一种基于一些基本的定义原则(如上所列)思考软件构建的方式。固然,编程范示的其余示例也包括面向对象编程和过程编程。
函数式的代码每每比命令式或面向对象的代码更简洁,更可预测,更容易测试 - 但若是不熟悉它以及与之相关的常见模式,函数式的代码也可能看起来更密集杂乱,而且 相关文献对新人来讲是很差理解的。
JavaScript支持闭包和高阶函数是函数式编程语言的特色。
高阶函数只是将函数做为参数或返回值的函数。
function higherOrderFunction(param,callback){ return callback(param); }
在JavaScript中,函数不只拥有一切传统函数的使用方式(声明和调用),并且能够作到像简单值同样赋值(var func = function(){})
、传参(function func(x,callback){callback();})
、返回(function(){return function(){}})
,这样的函数也称之为第一级函数(First-class Function)。不只如此,JavaScript中的函数还充当了类的构造函数的做用,同时又是一个Function
类的实例(instance)。这样的多重身份让JavaScript的函数变得很是重要。
Array.prototype.map 方法
map()
方法建立一个新数组,其结果是该数组中的每一个元素都调用一个提供的函数后返回的结果。
function map(arr, mapCallback) { // 首先,检查传递的参数是否正确。 if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { return []; } else { let result = []; // 每次调用此函数时,咱们都会建立一个 result 数组 // 由于咱们不想改变原始数组。 for (let i = 0, len = arr.length; i < len; i++) { result.push(mapCallback(arr[i], i, arr)); // 将 mapCallback 返回的结果 push 到 result 数组中 } return result; } }
Array.prototype.filter
方法filter()
方法建立一个新数组, 其包含经过所提供函数实现的测试的全部元素。
function filter(arr, filterCallback) { // 首先,检查传递的参数是否正确。 if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') { return []; } else { let result = []; // 每次调用此函数时,咱们都会建立一个 result 数组 // 由于咱们不想改变原始数组。 for (let i = 0, len = arr.length; i < len; i++) { // 检查 filterCallback 的返回值是不是真值 if (filterCallback(arr[i], i, arr)) { // 若是条件为真,则将数组元素 push 到 result 中 result.push(arr[i]); } } return result; // return the result array } }
Array.prototype.reduce
方法reduce()
方法对数组中的每一个元素执行一个由您提供的reducer
函数(升序执行),将其结果汇总为单个返回值。
function reduce(arr, reduceCallback, initialValue) { // 首先,检查传递的参数是否正确。 if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function') { return []; } else { // 若是没有将initialValue传递给该函数,咱们将使用第一个数组项做为initialValue let hasInitialValue = initialValue !== undefined; let value = hasInitialValue ? initialValue : arr[0]; 、 // 若是有传递 initialValue,则索引从 1 开始,不然从 0 开始 for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) { value = reduceCallback(value, arr[i], i, arr); } return value; } }
arguments
对象是函数中传递的参数值的集合。它是一个相似数组的对象,由于它有一个length属性,咱们可使用数组索引表示法arguments[1]
来访问单个值,但它没有数组中的内置方法,如:forEach
、reduce
、filter
和map
。
咱们可使用Array.prototype.slice
将arguments
对象转换成一个数组。
function one() { return Array.prototype.slice.call(arguments); }
注意:箭头函数中没有arguments
对象。
function one() { return arguments; } const two = function () { return arguments; } const three = function three() { return arguments; } const four = () => arguments; four(); // Throws an error - arguments is not defined
当咱们调用函数four
时,它会抛出一个ReferenceError: arguments is not defined error
。使用rest
语法,能够解决这个问题。
const four = (...args) => args;
这会自动将全部参数值放入数组中。
咱们可使用Object.create
方法建立没有原型的对象。
const o1 = {}; console.log(o1.toString()); // [object Object] const o2 = Object.create(null); console.log(o2.toString()); // throws an error o2.toString is not a function
b
会变成一个全局变量?function myFunc() { let a = b = 0; } myFunc();
缘由是赋值运算符是从右到左的求值的。这意味着当多个赋值运算符出如今一个表达式中时,它们是从右向左求值的。因此上面代码变成了这样:
function myFunc() { let a = (b = 0); } myFunc();
首先,表达式b = 0
求值,在本例中b
没有声明。所以,JS引擎在这个函数外建立了一个全局变量b
,以后表达式b = 0
的返回值为0
,并赋给新的局部变量a
。
咱们能够经过在赋值以前先声明变量来解决这个问题。
function myFunc() { let a,b; a = b = 0; } myFunc();
ECMAScript 是编写脚本语言的标准,这意味着JavaScript遵循ECMAScript标准中的规范变化,由于它是JavaScript的蓝图。
ECMAScript 和 Javascript,本质上都跟一门语言有关,一个是语言自己的名字,一个是语言的约束条件
只不过发明JavaScript的那我的(Netscape公司),把东西交给了ECMA(European Computer Manufacturers Association),这我的规定一下他的标准,由于当时有java语言了,又想强调这个东西是让ECMA这我的定的规则,因此就这样一个神奇的东西诞生了,这个东西的名称就叫作ECMAScript。
javaScript = ECMAScript + DOM + BOM(自认为是一种广义的JavaScript)
ECMAScript说什么JavaScript就得作什么!
JavaScript(狭义的JavaScript)作什么都要问问ECMAScript我能不能这样干!若是不能我就错了!能我就是对的!
——忽然感受JavaScript好没有尊严,为啥要搞我的出来约束本身,
那我的被创造出来也好委屈,本身被创造出来彻底是由于要约束JavaScript。
因为篇幅过长,我将此系列分红上下二篇,下篇咱们在见。
代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug。
原文:https://dev.to/macmacky/70-ja...
文章每周持续更新,能够微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq44924588... 已经收录,整理了不少个人文档,欢迎Star和完善,你们面试能够参照考点复习,另外关注公众号,后台回复福利,便可看到福利,你懂的。