( 译、持续更新 ) JavaScript 上分小技巧(四)

时间:2022-03-20 04:13:35

后续如有内容,本篇将会照常更新并排满15个知识点,以下是其他几篇译文的地址:

第一篇地址:( 译、持续更新 ) JavaScript 上分小技巧(一)

第二篇地址:( 译、持续更新 ) JavaScript 上分小技巧(二)

第三篇地址:( 译、持续更新 ) JavaScript 上分小技巧(三)

#59 - ES6,var vs let

var关键字定义的变量根据定义的环境用于function内,function外或者全局;而let定义的变量只用于"块"范围。

function varvslet() {
console.log(i); // i is undefined 由于变量提升
// console.log(j); // ReferenceError: j is not defined for( var i = 0; i < 3; i++ ) {
console.log(i); // 0, 1, 2
}; console.log(i); //
// console.log(j); // ReferenceError: j is not defined for( let j = 0; j < 3; j++ ) {
console.log(j);
}; console.log(i); //
// console.log(j); // ReferenceError: j is not defined
}

从上面代码可以看出,let不会得到提升,且仅用于该变量使用的块范围。

let存在自己封闭的块范围,并且在循环场景中会确保在上一次循环迭代结束重新分配它的值:

for (var i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i); // 由于异步,输出 5, 5, 5, 5, 5
}, 100);
} for (let i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i); // 输出 0, 1, 2, 3, 4
}, 100);
}

参考资料:

Let keyword vs var keyword

For and against let

Explanation of let and block scoping with for loops

#58 - 跳出/继续执行循环语句

对于for循环,我们通常用break来跳出循环:

const a = [0, 1, 2, 3, 4];
for (var i = 0; i < a.length; i++) {
if (a[i] === 2) {
break; // stop the loop
}
console.log(a[i]);
}
//> 0, 1

在forEach中,就不能用break来跳出循环了,与之功能最相近的是return了:

[0, 1, 2, 3, 4].forEach(function(val, i) {
if (val === 2) {
// 我们该怎么跳出循环呢?
return true;
}
console.log(val);
});
//> 0, 1, 3, 4

.some是Array原型上的一个方法,用于检测该数组中是否存在满足提供的规则的元素,当检测到元素满足条件并且返回true,则会停止继续执行。.some MDN详细资料

const isBiggerThan10 = numb => numb > 10;

[2, 5, 8, 1, 4].some(isBiggerThan10);  // false
[12, 5, 8, 1, 4].some(isBiggerThan10); // true [0, 1, 2, 3, 4].some(function(val, i) {
if (val === 2) {
return true;
}
console.log(val); // your code
});
//> 0, 1

使用.some,实现了和forEach相似的功能,并且在满足给定条件时会跳出循环。

使用.some,我们必须返回相反的值。当return的是false,循环会继续执行。

const isTwoPresent = [0, 1, 2, 3, 4].some(function(val, i) {
if (val === 2) {
return true; // break
}
console.log(val);
});
console.log(isTwoPresent);
//> true // 当return的是true时,console.log(val)会输出:0,1;当return的是false时,console.log(val)会输出0,1,3,4

#57 - JavaScript中的逗号运算符

众所周知,逗号运算符可以作为分隔符抑或同时执行多个变量声明,如:

for(var i=0, j=0; i<5; i++, j++, j++){
console.log("i:"+i+", j:"+j);
}
// 输出:
i:0, j:0
i:1, j:2
i:2, j:4
i:3, j:6
i:4, j:8

然而当放在表达式中,他会从左到右的评估每一个表达式,并且返回最右表达式的结果。

function a(){console.log('a'); return 'a';}
function b(){console.log('b'); return 'b';}
function c(){console.log('c'); return 'c';} var x = (a(), b(), c());
console.log(x);
// 输出:
"a"
"b"
"c" "c"

注意:逗号运算符在JavaScript中优先级是最低的,所以,当没有大括号的时候,表达式会变成:var (x = a()), b(), c();

#56 - JavaScript 拷贝到剪切板

现在有个需求,我们想要将字符串拷贝到剪切板中以供粘贴,用js怎么操作呢?

其实实现起来很简单,我们需要一个input来作为拷贝内容的载体,然后选择拷贝内容,执行execCommand命令即可。execCommand('copy')命令会将实际选中的值拷贝到剪切板,当然,也可以是cut,paste等命令。

<input type="text" class="copy" value="hello world" />
<input type="text" class="paste" />
document.querySelector('.do-copy').addEventListener('click',function(e){
document.querySelector('.copy').select();
document.execCommand('copy');
},!1)

参考资料:execCommand MDN

然而发现各种测paste命令,好像存在问题,*上的解答也不尽人意...(没能满意的跑顺畅)

### 2016-10-02 更新 ###

# 55 - 让数组进行多次循环

有些时候,我们需要对一个数组进行多次循环。以下代码展示了如何进行一个数组的循环。

var aList = ['A','B','C','D','E'];

function make_looper( arr ){

    arr.loop_idx = 0;

    // 返回当前循环到的元素
arr.current = function(){ if( this.loop_idx < 0 ){// 第一次验证
this.loop_idx = this.length - 1;// 更新循环索引
} if( this.loop_idx >= this.length ){// 第二次验证
this.loop_idx = 0;// 更新循环索引
} return arr[ this.loop_idx ];//return item
}; // 对循环索引进行一次加操作,并且返回当前元素
arr.next = function(){
this.loop_idx++;
return this.current();
};
// 对循环索引进行一次减操作,并且返回当前元素
arr.prev = function(){
this.loop_idx--;
return this.current();
};
} make_looper( aList); aList.current();// -> A
aList.next();// -> B
aList.next();// -> C
aList.next();// -> D
aList.next();// -> E
aList.next();// -> A
aList.pop() ;// -> E
aList.prev();// -> D
aList.prev();// -> C
aList.prev();// -> B
aList.prev();// -> A
aList.prev();// -> D

使用%操作符便更好了:

var aList = ['A','B','C','D','E'];

function make_looper( arr ){

    arr.loop_idx = 0;

    // 返回当前循环到的元素
arr.current = function(){
this.loop_idx = ( this.loop_idx ) % this.length;// 没有验证 !!
return arr[ this.loop_idx ];
}; // 对循环索引进行一次加操作,并且返回当前元素
arr.next = function(){
this.loop_idx++;
return this.current();
}; // 对循环索引进行一次减操作,并且返回当前元素
arr.prev = function(){
this.loop_idx += this.length - 1;
return this.current();
};
} make_looper( aList); aList.current();// -> A
aList.next();// -> B
aList.next();// -> C
aList.next();// -> D
aList.next();// -> E
aList.next();// -> A
aList.pop() ;// -> E
aList.prev();// -> D
aList.prev();// -> C
aList.prev();// -> B
aList.prev();// -> A
aList.prev();// -> D

# 53 - 获取文件扩展名

如何获取文件扩展名?

var file1 = "50.xsl";
var file2 = "30.doc";
getFileExtension(file1); //return xsl
getFileExtension(file2); //return doc function getFileExtension(filename) {
/*TODO*/
}

解决方案一:

function getFileExtension1(filename) {
return (/[.]/.exec(filename)) ? /[^.]+$/.exec(filename)[0] : undefined;
}

解决方案二:

function getFileExtension2(filename) {
return filename.split('.').pop();
}

然而这两个解决方案并不能满足一些特殊情况,下面是一个更好的解决方案。

解决方案三(使用slice和lastIndexOf):

function getFileExtension3(filename) {
return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);
} console.log(getFileExtension3('')); // ''
console.log(getFileExtension3('filename')); // ''
console.log(getFileExtension3('filename.txt')); // 'txt'
console.log(getFileExtension3('.hiddenfile')); // ''
console.log(getFileExtension3('filename.with.many.dots.ext')); // 'ext'

日常解释以上代码:

. String.lastIndexOf() 方法返回指定值的最后一个符合条件的值(在此案例中是"."),如果没有值则返回-1。

. "filename"和".hiddenfile"执行"lastIndexOf"方法后返回的值分别是0和-1。无符号右位移操作符 将-1转化为4294967295,-2转化为4294967294,这个小技巧用以确保在一些特殊情况下文件名不会被改变(当取"."的lastIndexOf为0或者-1时,截取的便是整个文件名,无符号右位移2位,这时则变成了Integer的最大值,免去了"没有扩展名、只有扩展名"这两个条件的判断)。

. String.prototype.slice() 根据上面的计算方式得出的index来提取文件扩展名。如果返回的index值大与文件名的长度,则返回""。

解决方案四(使用正则,好友 @chengby 提供):

function getExt(file){
return file.match(/\S+\.+(\S*)/)?file.match(/\S+\.+(\S*)/)[1]:"";
}

对比:

解决方案一
''                                                 undefined
'filename'                                     undefined
'filename.txt'                                'txt'
'.hiddenfile'                                  'hiddenfile'
'filename.with.many.dots.ext'        'ext'
解决方案二
''                                                ''
'filename'                                    'filename'
'filename.txt'                               'txt'
'.hiddenfile'                                 'hiddenfile'
'filename.with.many.dots.ext'        'ext'
解决方案三
''                                                 ''
'filename'                                     ''
'filename.txt'                                'txt'
'.hiddenfile'                                  ''
'filename.with.many.dots.ext'        'ext'

解决方案四
''                                                 ''
'filename'                                     ''
'filename.txt'                                'txt'
'.hiddenfile'                                  ''
'filename.with.many.dots.ext'        'ext'

### 2016-06-02 更新 ###

# 52 - "new"操作的返回值(构造函数的返回值)

在一些情况下,我们会使用"new"来为对象指定一个新的实例。本章将展示一些使用"new" 生成实例背后发生的事情。

"new" 在JavaScript中是一个操作符,在合理的情况下会返回一个对象的实例。这意味着我们需要一个构造函数:

function Thing() {
this.one = 1;
this.two = 2;
} var myThing = new Thing(); myThing.one //
myThing.two //

注意:this 指向的是通过"new"所创建的新对象。如过直接调用Thing(),而不是通过new 操作,则不会创建对象,这时候的this 指向的是全局对象,也就是window 。

1.这时候你会突然发现多了2个全局变量,one和two。

2.myThing 现在是undefined,因为Thing()没有返回任何值。

现在,我们得到下一个例子,看着不怎么可靠的例子。这里我们对构造函数加了些料:

function Thing() {
this.one = 1;
this.two = 2; return 5;
} var myThing = new Thing();

现在myThing等于什么呢?5?一个对象?

结果是:

myThing.one //
myThing.two //

有趣的是,我们并没有看到理应从构造函数中返回的数值5 。这很奇怪,不是么?函数发生了什么?5在哪?那么再试试其他的看看。

我们return一个非原始类型看看,返回一个对象:

function Thing() {
this.one = 1;
this.two = 2; return {
three: 3,
four: 4
};
} var myThing = new Thing();

我们console下,看看是什么样的结果:

console.log(myThing);
/**************************************************
** Object {three: 3, four: 4}
** this.one 和 this.two 竟然不见了,发生了什么?
***************************************************/

这里正是我们需要学习的:当你结合"new"关键字调用一个函数的时候,你可以使用this 关键字设置属性(你可能已经知道了)。如果使用new关键字操作存在返回原始值的函数,将不会得到你所期望的值,而是返回这个函数所构造的实例(你所设置的属性,如 this.one = 1 ; )

然而,当返回值是非原始类型的时候,如object、array,或者function,那么将覆盖这个实例,并且返回这个非原始类型的值,完全将你对这个this上的属性指定给覆盖了。

### 2016-04-14 更新 ###

#51 - 轻松的监听DOM事件

大部分人依旧这么做:

· element.addEventListener('type', obj.method.bind(obj))
· element.addEventListener('type', function (event) {})
· element.addEventListener('type', (event) => {})

上面的例子都可以创建一个新的匿名事件处理,当这些事件不再被需要的时候,不会被删除。当不再需要的事件被用户意外操作或者事件冒泡而触发,则可能导致性能问题或意外的逻辑bug。

安全的事件处理模式应当如下:

参考使用

const handler = function () {
console.log("Tada!")
}
element.addEventListener("click", handler)
// 之后
element.removeEventListener("click", handler)

对函数命名并且移除事件

element.addEventListener('click', function click(e) {
if (someCondition) {
return e.currentTarget.removeEventListener('click', click);
}
});

更好的方法:

function handleEvent (eventName, {onElement, withCallback, useCapture = false} = {}, thisArg) {
const element = onElement || document.documentElement function handler (event) {
if (typeof withCallback === 'function') {
withCallback.call(thisArg, event)
}
} handler.destroy = function () {
return element.removeEventListener(eventName, handler, useCapture)
} element.addEventListener(eventName, handler, useCapture)
return handler
} // 需要用到的时候
const handleClick = handleEvent('click', {
onElement: element,
withCallback: (event) => {
console.log('Tada!')
}
}) // 需要移除的时候
handleClick.destroy()

### 2016-03-21 更新 ###

#50 - 实用的控制台打印技巧

在执行代码的时候,你可以想要知道函数中某个变量值是否发生改变抑或查看代码执行是否有效,很多人习惯用"alert"来在适当的地方弹出显示,然而这是不方便的,一方面alert能显示的结果有限(如打印object时将会是[object Object]),另一方面它阻碍了代码的继续执行。

我们应该更需要去熟悉使用浏览器的开发工具--Console(用于显示数据)和Sources(用于断点跟踪)。这里我们不多做介绍,只给出相应的图片展示吧:

console.log(value);

( 译、持续更新 ) JavaScript 上分小技巧(四)

使用断点:

( 译、持续更新 ) JavaScript 上分小技巧(四)

更多学习应用控制台的文章:Debugging JavaScript(Chrome) 、 Set a conditional breakpoint(FireFox) 、 Debugger(Edge) 、Firebug控制台详解

#49 - JS中最简单的方式获取时间戳

我们经常需要获取时间戳进行计算,有好几种方式来获取时间戳,目前最简单也是最快的方法是:

const timestamp = Date.now();

或者

const timestamp = new Date().getTime();

获取具体时间 yyyy-mm-dd 的时间戳,我们可以通过给Date构造函数传入一个参数,例如:

const timestamp = new Date('2012-06-08').getTime()

也可以在声明日期对象时添加一个+符号,如下所示:

const timestamp = +new Date()

或者获取指定日期的时间戳

const timestamp = +new Date('2012-06-08')

引擎中调用了Date对象valueOf方法,返回一元运算符"+"配合返回的值调用toNumber()。更多详细说明请查看以下链接:

Date.prototype.valueOf 
一元运算符"+" 
toNumber()

#48 - 内置函数reduce的用法
正如文档所写,reduce()方法应用一个函数以针对数组的值(从左到右)减至最后一个值。
reduce()
reduce()方法接收两个参数(M:mandatory,O:options):
 (M)一个回调函数,用于处理与先前的计算结果和下一个元素。
 (O)被作为回调函数的第一个调用的参数的初始值。
所以,我们来看看普通用法,后面再来一个更先进的。
常见的用法(积累,连接)
假如我们在购物,并且购物车足够满,让我们计算价格总和:

// 现在的价格
var items = [{price: 10}, {price: 120}, {price: 1000}];
// reducer函数
var reducer = function add(sumSoFar, nextPrice) { return sumSoFar + nextPrice.price; };
// 处理...
var total = items.reduce(reducer, 0);
console.log(total); //

函数的可选参数在一般情况下是0,它可以是个对象,也可以是个数组。
现在,我们获得一张20元的代金券:

var total = items.reduce(reducer,-20);
console.log(total); //

高端用法(组合)
落后的思想是将reducers分别写成单独的功能,并最终计算出一个新的reducers的功能。
为了说明这一点,让我们创建一个对象与一些能够计算不同货币美元的总价格的reducers功能。

var reducers = {
totalInDollar: function(state, item) {
state.dollars += item.price;
return state;
},
totalInEuros : function(state, item) {
state.euros += item.price * 0.897424392;
return state;
},
totalInYen : function(state, item) {
state.yens += item.price * 113.852;
return state;
},
totalInPounds : function(state, item) {
state.pounds += item.price * 0.692688671;
return state;
}
// 更多...
};

然后,我们创建一个新的功能:
 · 负责应用reduce的各部分功能。
 · 将返回一个新的回调函数。

var combineTotalPriceReducers = function(reducers) {
return function(state, item) {
return Object.keys(reducers).reduce(
function(nextState, key) {
reducers[key](state, item);
return state;
},
{}
);
}
};

现在,让我们看看怎么使用这个:

var bigTotalPriceReducer = combineTotalPriceReducers(reducers);
var initialState = {dollars: 0, euros:0, yens: 0, pounds: 0};
var totals = items.reduce(bigTotalPriceReducer, initialState);
console.log(totals);
/*
Object {dollars: 1130, euros: 1015.11531904, yens: 127524.24, pounds: 785.81131152}
*/

我希望这个方法能够在你所需要的时候给你提供一个reduce使用的思路。
#47 - 基本声明
下面是javascript中声明变量的不同方式。console.log能够很好的解释这里将发生什么。

var y, x = y = 1 //== var x; var y; x = y = 1
console.log('_> 1:', `x = ${x}, y = ${y}`)
// 将会打印
//_> 1: x = 1, y = 1

首先,我们设置两个变量,在这里没更多的操作。

;(() => {
var x = y = 2 // == var x; y = 2;
console.log('2.0:', `x = ${x}, y = ${y}`)
})()
console.log('_> 2.1:', `x = ${x}, y = ${y}`)
// 将会打印
//2.0: x = 2, y = 2
//_> 2.1: x = 1, y = 2

正如你所见的,这里的代码只改变了全局的y,因为我们没有在闭包中声明变量。

;(() => {
var x, y = 3 // == var x; var y = 3;
console.log('3.0:', `x = ${x}, y = ${y}`)
})()
console.log('_> 3.1:', `x = ${x}, y = ${y}`)
// 将会打印
//3.0: x = undefined, y = 3
//_> 3.1: x = 1, y = 2

现在我们通过var来声明变量。这意味着他们只存在封闭的语境中。

;(() => {
var y, x = y = 4 // == var x; var y; x = y = 4
console.log('4.0:', `x = ${x}, y = ${y}`)
})()
console.log('_> 4.1:', `x = ${x}, y = ${y}`)
// 将会打印
//4.0: x = 4, y = 4
//_> 4.1: x = 1, y = 2

这两个变量都通过var声明并且只会给定了值。因为local>global,所以x和y在本地封闭语境中,意味着全局的x和y没做改变。

x = 5 // x = 5
console.log('_> 5:', `x = ${x}, y = ${y}`)
// 将会打印
//_> 5: x = 5, y = 2

这最后一行是明确的。
更多的变量相关信息:MDN
#46 - js纯粹的检测文档加载完毕
使用javascript的 readyState 以跨浏览器的方式来检测文档是否加载。

if (document.readyState === 'complete') {
// 页面已经完全加载
}

你能够检查文档是否加载...

let stateCheck = setInterval(() => {
if (document.readyState === 'complete') {
clearInterval(stateCheck);
// document ready
}
}, 100);

或者使用onreadystatechange...

document.onreadystatechange = () => {
if (document.readyState === 'complete') {
// document ready
}
};

使用document.readyState === 'interactive'检测DOM是否ready。
          ### 2016-02-18 更新 ###