上面还是打印16
,因为bar
内的代码仍然可以引用参数x
和变量tmp
,即使它们不再直接的作用域内。
但是,由于tmp
仍然在bar
的闭包内部徘徊,因此可以对其进行递增。 每次调用bar时,它将增加1
。
闭包最简单的例子是这样的:
var a = 10;
function test() {
console.log(a); // will output 10
console.log(b); // will output 6
}
var b = 6;
test();
当调用一个JavaScript函数时,将创建一个新的执行上下文ec
。连同函数参数和目标对象,这个执行上下文还接收到调用执行上下文的词法环境的链接,这意味着在外部词法环境中声明的变量(在上面的例子中,a
和b
)都可以从ec
获得。
每个函数都会创建一个闭包,因为每个函数都有与其外部词法环境的链接。
注意,变量本身在闭包中是可见的,而不是副本。
4. use strict 在 JavaScript 中做了什么,背后的原因是什么
引用一些有趣的部分:
严格模式是ECMAScript 5中的一个新特性,它允许我们将程序或函数放置在严格的操作上下文中。这种严格的上下文会防止某些操作被执行,并引发更多异常。
严格模式在很多方面都有帮助:
-
它捕获了一些常见的编码漏洞,并抛出异常。
-
当采取相对不安全的操作(例如访问全局对象)时,它可以防止错误或抛出错误。
-
它禁用令人困惑或考虑不周到的特性。
另外,请注意,我信可以将“strict mode”
应用于整个文件,也可以仅将其用于特定函数。
// Non-strict code…
(function(){
“use strict”;
// Define your library strictly…
})();
// Non-strict code…
如果是在混合使用旧代码和新代码的情况,这可能会有所帮助。它有点像在Perl中使用的“use strict”。通过检测更多可能导致损坏的东西,帮助我们减少更多的错误。
现在所有主流浏览器都支持严格模式。
在原生ECMAScript模块(带有import
和export
语句)和ES6类中,严格模式始终是启用的,不能禁用。
ECMAScript 6 引入了string .prototype.include
const string = “foo”;
const substring = “oo”;
console.log(string.includes(substring));
不过,IE 不支持 includes
。在 CMAScript 5或更早的环境中,使用String.prototype.indexOf
。如果找不到子字符串,则返回-1
:
var string = “foo”;
var substring = “oo”;
console.log(string.indexOf(substring) !== -1);
为了使其在旧的浏览器中运行,可以使用这种polyfill
:
if (!String.prototype.includes) {
String.prototype.includes = function(search, start) {
‘use strict’;
if (typeof start !== ‘number’) {
start = 0;
}
if (start + search.length > this.length) {
return false;
} else {
return this.indexOf(search, start) !== -1;
}
};
}
6. var functionName = function() {} 与 function functionName() {}
不同之处在于functionOne
是一个函数表达式,因此只在到达这一行时才会定义,而functionTwo
是一个函数声明,在它周围的函数或脚本被执行(由于提升)时就定义。
如,函数表达式
// TypeError: functionOne is not a function
functionOne();
var functionOne = function() {
console.log(“Hello!”);
};
函数声明:
// “Hello!”
functionTwo();
function functionTwo() {
console.log(“Hello!”);
}
过去,在不同的浏览器之间,在块中定义的函数声明的处理是不一致的。严格模式(在ES5中引入)解决了这个问题,它将函数声明的范围限定在其封闭的块上。
‘use strict’;
{ // note this block!
function functionThree() {
console.log(“Hello!”);
}
}
functionThree(); // ReferenceError
function abc(){}
也具有作用域-名称abc
在遇到该定义的作用域中定义。 例:
function xyz(){
function abc(){};
// abc 在这里定义…
}
// …不是在这里
如果想在所有浏览器上给函数起别名,可以这么做:
function abc(){};
var xyz = abc;
在本例中,xyz和abc都是同一个对象的别名
console.log(xyz === abc) // true
它的名称是自动分配的。但是当你定义它的时候
var abc = function(){};
console.log(abc.name); // “”
它的name
称为空,我们创建了一个匿名函数并将其分配给某个变量。使用组合样式的另一个很好的理由是使用简短的内部名称来引用自身,同时为外部用户提供一个长而不会冲突的名称:
// 假设 really.long.external.scoped 为 {}
really.long.external.scoped.name = function shortcut(n){
// 它递归地调用自己:
shortcut(n - 1);
// …
// 让它自己作为回调传递::
someFunction(shortcut);
// …
}
在上面的例子中,我们可以对外部名称进行同样的操作,但是这样做太笨拙了(而且速度更慢)。另一种引用自身的方法是arguments.callee
,这种写法也相对较长,并且在严格模式中不受支持。
实际上,JavaScript对待这两个语句是不同的。下面是一个函数声明:
function abc(){}
这里的abc
可以定义在当前作用域的任何地方:
// 我们可以在这里调用
abc();
// 在这里定义
function abc(){}
// 也可以在这里调用
abc();
此外,尽管有 return
语句,也可以提升:
// 我们可以在这里调用
abc();
return;
function abc(){}
下面是一个函数表达式:
var xyz = function(){};
这里的xyz
是从赋值点开始定义的:
// 我们不可以在这里调用
xyz();
// 在这里定义 xyz
xyz = function(){}
// 我们可以在这里调用
xyz();
函数声明与函数表达式之间存在差异的真正原因。
var xyz = function abc(){};
console.log(xyz.name); // “abc”
就个人而言,我们更喜欢使用函数表达式声明,因为这样可以控制可见性。当我们像这样定义函数时:
var abc = function(){};
我们知道,如果我们没有在作用域链的任何地方定义abc
,那么我们是在全局作用域内定义的。即使在eval()
内部使用,这种类型的定义也具有弹性。而定义:
function abc(){};
取决于上下文,并且可能让你猜测它的实际定义位置,特别是在eval()
的情况下,—取决于浏览器。
我们可以这样删除对象的属性:
delete myObject.regex;
// 或者
delete myObject[‘regex’];
// 或者
var prop = “regex”;
delete myObject[prop];
事例:
var myObject = {
“ircEvent”: “PRIVMSG”,
“method”: “newURI”,
“regex”: “^http://.*”
};
delete myObject.regex;
console.log(myObject);
JavaScript 中的对象可以看作键和值之间的映射。delete
操作符用于一次删除一个键(通常称为对象属性)。
var obj = {
myProperty: 1
}
console.log(obj.hasOwnProperty(‘myProperty’)) // true
delete obj.myProperty
console.log(obj.hasOwnProperty(‘myProperty’)) // false
delete
操作符不是直接释放内存,它不同于简单地将null
或undefined
值赋给属性,而是将属性本身从对象中删除。
注意,如果已删除属性的值是引用类型(对象),而程序的另一部分仍然持有对该对象的引用,那么该对象当然不会被垃圾收集,直到对它的所有引用都消失。
delete
只对其描述符标记为configurable
的属性有效。
8. JS 的比较中应使用哪个等于运算符(== vs ===)?
严格相等运算符(===
)的行为与抽象相等运算符(==
)相同,除非不进行类型转换,而且类型必须相同才能被认为是相等的。
==
运算符会进行类型转换后比较相等性。 ===
运算符不会进行转换,因此如果两个值的类型不同,则===
只会返回false。
JavaScript有两组相等运算符:===
和!==
,以及它们的孪生兄弟==
和!=
。如果这两个操作数具有相同的类型和相同的值,那么===
的结果就是 true
,而!==
的结果就是 false
。
下面是一些事例:
‘’ == ‘0’ // false
0 == ‘’ // true
0 == ‘0’ // true
false == ‘false’ // false
false == ‘0’ // true
false == undefined // false
false == null // false
null == undefined // true
’ \t\r\n ’ == 0 // true
上面有些看起来会挺困惑的,所以尽量还是使用严格比较运算符(===
)。对于引用类型,==
和===
操作一致(特殊情况除外)。
var a = [1,2,3];
var b = [1,2,3];
var c = { x: 1, y: 2 };
var d = { x: 1, y: 2 };
var e = “text”;
var f = “te” + “xt”;
a == b // false
a === b // false
c == d // false
c === d // false
e == f // true
e === f // true
特殊情况是,当你将一个字符串字面量与一个字符串对象进行比较时,由于该对象的toString
或valueOf
方法,该对象的值与相字面量的值一样。
考虑将字符串字面量与由String
构造函数创建的字符串对象进行比较:
“abc” == new String(“abc”) // true
“abc” === new String(“abc”) // false
在这里,==
操作符检查两个对象的值并返回true
,但是=
==看到它们不是同一类型并返回false
。哪一个是正确的?这取决于你想要比较的是什么。
我们的建议是完全绕开该问题,只是不要使用String
构造函数来创建字符串对象。
使用==运算符(等于)
true == 1; //true, 因为 true 被转换为1,然后进行比较
“2” == 2; //true, 因为 “2” 被转换成 2,然后进行比较
使用===操作符
true === 1; //false
“2” === 2; //false
9.在 JavaScript 中深拷贝一个对象的最有效方法是什么?
快速克隆,数据丢失– JSON.parse/stringify
如果您没有在对象中使用Date
、函数、undefined
、Infinity
、RegExp
、Map
、Set
、blob、、稀疏数组、类型化数组或其他复杂类型,那么可以使用一行简单代码来深拷贝一个对象:
JSON.parse(JSON.stringify(object))
const a = {
string: ‘string’,
number: 123,
bool: false,
nul: null,
date: new Date(),
undef: undefined, // 丢失
inf: Infinity, // 被设置为 null
re: /.*/, // 丢失
}
console.log(a);
console.log(typeof a.date); // object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
/*
object
{
string: ‘string’,
number: 123,
bool: false,
nul: null,
date: ‘2020-09-04T00:45:41.823Z’,
inf: null,
re: {}
}
*/
console.log(typeof clone.date); // string
使用库进行可靠的克隆
由于克隆对象不是一件简单的事情(复杂类型、循环引用、函数等等),大多数主要的库都提供了拷贝对象的函数。如果你已经在使用一个库,请检查它是否具有对象克隆功能。例如
-
lodash –
cloneDeep
; 可以通过lodash.clonedeep
模块单独导入,如果你尚未使用提供深拷贝功能的库,那么它可能是你的最佳选择 -
AngularJS –
angular.copy
-
jQuery –
jQuery.extend(true, { }, oldObject)
;.clone()
仅克隆DOM元素
ES6
ES6 提供了两种浅拷贝机制:Object.assign()
和spread
语法。它将所有可枚举的自有属性的值从一个对象复制到另一个对象。例如
var A1 = {a: “2”};
var A2 = Object.assign({}, A1);
var A3 = {…A1}; // Spread Syntax
在以前的测试中,速度是最主要的问题
JSON.parse(JSON.stringify(obj))
这是深拷贝对象的最慢方法,它比jQuery.extend
慢 10-20%。
当deep
标志设置为false
(浅克隆)时,jQuery.extend
非常快。 这是一个不错的选择,因为它包括一些用于类型验证的额外逻辑,并且不会复制未定义的属性等,但这也会使你的速度变慢。
如果想拷贝的一个对象且你知道对象结构。那么,你可以写一个简单的for (var i in obj)
循环来克隆你的对象,同时检查hasOwnProperty
,这将比jQuery快得多。
var clonedObject = {
knownProp: obj.knownProp,
…
}
注意在 Date
对象JSON上使用JSON.parse(JSON.stringify(obj))
方法。JSON.stringify(new Date())
以ISO格式返回日期的字符串表示,JSON.parse()
不会将其转换回Date
对象。
10.如何在另一个JavaScript文件中包含一个JavaScript文件?
旧版本的JavaScript没有import
、include
或require
,因此针对这个问题开发了许多不同的方法。
但是从2015年(ES6)开始,JavaScript已经有了ES6模块标准,可以在Node中导入模块。为了与旧版浏览器兼容,可以使用Webpack和Rollup之类的构建工具和/或Babel这样的编译工具。
ES6 Module
从v8.5开始,Node.js就支持ECMAScript (ES6)模块,带有--experimental-modules
标志,而且至少Node.js v13.8.0没有这个标志。要启用ESM(相对于Node.js之前的commonjs风格的模块系统[CJS]),你可以在 package.json
中使用“type”:“module”
。或者为文件提供扩展名.mjs
。(类似地,如果默认为ESM,则用 Node.js 以前的CJS模块编写的模块可以命名为.cjs
。)
使用package.json
:
{
“type”: “module”
}
在 module.js:
中
export function hello() {
return “Hello”;
}
main.js:
import { hello } from ‘./module.js’;
let val = hello(); // val is “Hello”;
使用.mjs
,会有对应的module.mjs
:
export function hello() {
return “Hello”;
}
在main.mjs
中
import { hello } from ‘./module.mjs’;
let val = hello(); // val is “Hello”;
自Safari 10.1,Chrome 61,Firefox 60 和 Edge 16 开始,浏览器就已经支持直接加载ECMAScript模块(不需要像Webpack这样的工具)。无需使用Node.js的.mjs
扩展名; 浏览器完全忽略模块/脚本上的文件扩展名。
// hello.mjs – or it could be simply hello.js
export function hello(text) {
const div = document.createElement(‘div’);
div.textContent = Hello ${text}
;
document.body.appendChild(div);
}
大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】。
浏览器中的动态导入
动态导入允许脚本根据需要加载其他脚本
Node.js require
在 Node.js 中用的较多还是 module.exports/require
// mymodule.js
module.exports = {
hello: function() {
return “Hello”;
}
}
// server.js const myModule = require('./mymodule'); let val = myModule.hello(); // val is "Hello"
动态加载文件
我们可以通过动态创建 script
来动态引入文件:
function dynamicallyLoadScript(url) {
var script = document.createElement(“script”);
document.head.appendChild(script);
}
检测脚本何时执行
现在,有一个个大问题。上面这种动态加载都是异步执行的,这样可以提
真题解析、进阶学习笔记、最新讲解视频、实战项目源码、学习路线大纲
详情关注公中号【编程进阶路】
高网页的性能。 这意味着不能在动态加载下马上使用该资源,因为它可能还在加载。
例如:my_lovely_script.js
包含MySuperObject
:
var js = document.createElement(“script”);
js.type = “text/javascript”;
最后
大厂面试问深度,小厂面试问广度,如果有同学想进大厂深造一定要有一个方向精通的惊艳到面试官,还要平时遇到问题后思考一下问题的本质,找方法解决是一个方面,看到问题本质是另一个方面。还有大家一定要有目标,我在很久之前就想着以后一定要去大厂,然后默默努力,每天看一些大佬们的文章,总是觉得只有再学深入一点才有机会,所以才有恒心一直学下去。