1、宏任务微任务
console.log(1);
setTimeout(() => {
console.log(2);
process.nextTick(() => {
console.log(3);
});
new Promise((resolve) => {
console.log(4);
resolve();
}).then(() => {
console.log(5);
});
});
new Promise(resolve => {
resolve();
}).then(() => {
process.nextTick(() => {
console.log(6);
})
new Promise(resolve => {
resolve();
}).then(() => {
console.log(7)
})
})
process.nextTick(() => {
console.log(8);
});
new Promise((resolve) => {
console.log(9);
resolve();
}).then(() => {
console.log(10);
});
setTimeout(() => {
console.log(11);
process.nextTick(() => {
console.log(12);
});
new Promise((resolve) => {
console.log(13);
}).then(() => {
console.log(14);
});
});
// 输出:1 9 8 10 7 6 2 4 3 5 11 13 12
2、
<script>
function side(arr) {
arr[0] = arr[2]
}
function a(a, b, c = 3) {
c = 10;
side(arguments);
return a + b + c;
}
console.log(a(1, 1, 1));
</script>
// 输出:12
解析: arguments中c的值还是1不会变成10
3、
var min = Math.min();
var max = Math.max();
console.log(min < max);
console.log(min > max);
// false
// true
解析:Math.min() 返回零个或多个数值中的最小值,如果任一参数不能转化为数值,则返回NaN,如果参数为空,则返回 Infinity;
Math.max() 返回零个或多个数值中的最大值,如果任一参数不能转化为数值,则返回NaN,如果参数为空,则返回 -Infinity
4、
<script>
var a = 1;
(function a() {
a = 2;
console.log(a);
})();
</script>
// 输出:ƒ a() {
a = 2;
console.log(a);
}
解析:立即调用的函数表达式(IIFE)有一个自己独立的作用域
IIFE:developer.mozilla.org/zh-CN/docs/…
5、写出执行结果(与上一题比较进行理解)
(function () {
var a = (b = 5);
})();
console.log(b);
console.log(a);
输出: // 5 Uncaught ReferenceError: a is not defined
// 需要注意的是在严格模式下,要显式的引用全局作用域:var a = (window.b = 5),
// 否则会报 Uncaught ReferenceError: b is not defined
6、this指向问题
var fullname = 'a';
var obj = {
fullname: 'b',
prop: {
fullname: 'c',
getFullname: function () {
return this.fullname;
}
}
}
console.log(obj.prop.getFullname());
var test = obj.prop.getFullname;
console.log(test());
// 输出:c a
解析:this的指向取决于函数的执行环境;1、getFullname()作为obj.prop对象的方法调用,此时执行环境为这个对象;2、getFullname() 被分配个test是执行环境变成了全局对象(window),因为test是在全局作用域中定义的
7、Object.create()和 delete操作符
<script>
var obj = {
hello: 'world'
}
var test = Object.create(obj);
delete test.hello;
console.log(test.hello);
</script>
// 输出:hello
解析:Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
delete 操作符用于删除对象的某个属性;如果没有指向这个属性的引用,那它最终会被释放。
delete操作只会在自身的属性上起作用delete在删除一个不可配置的属性时,在严格模式下会抛出异常,
非严格模式下返回false
delete能删除的:
(1)可配置对象属性
(2)隐式声明的全局变量
(3)用户定义的属性
(4)ES6中,通过const和let声明指定的TDZ(暂时性锁区)对delete也会起作用
delete不能删除的:
(1)显式声明的全局变量
(2)内置对象的内置属性
(3)原型链继承属性
delete删除数组元素:
删除一个元素时,数组的长度是不会变小,被删除的元素变成undefined
9、函数表达式
var foo = function bar() { return 12 };
console.log(bar());
// Uncaught ReferenceError: bar is not defined
解析:命名函数表达式函数只能在函数体内有效
10、写出执行结果
var x = 1;
if (function f(){}) {
x += typeof f
}
console.log(x);
// 输出:'1undefined'
解析:运算符中的函数声明在执行阶段是找不到的,typeof返回undefined
11、写出执行结果
function f() {
return f;
}
console.log(new f() instanceof f);
// 输出:false
解析:a instanceof b 用于检测a是否为b的实例,由于return f的存在,new f()得到的是f的函数对象,而非f的实例
12、写出执行结果
<script>
var foo = {
bar: function() {
return this.baz;
},
baz: 1
}
console.log(typeof (f = foo.bar)());
</script>
// 输出:undefined
解析:赋值给f,f()的执行环境为全局作用域,得到undefined,typeof undefined得到undefined(可与第6题搭配理解)
13、js模块化规范
一、script引入
即将所有js文件都简单的放在一起,由于每一个模块都暴漏在全局,会污染全局作用域,
并且依赖方式不明显
二、CommonJS规范:
通过require方法同步加载所依赖的其他模块,然后通过 exports 或 module.exports
来到处需要暴漏的接口(如下)
var i = 6;
var foo = function (v) {
return i + v
}
module.exports = {
i,
foo
}
CommonJS规范在服务端完成了 js 的模块化,
1、所有代码都运行在模块作用域,解决了依赖、全局污染的问题,(如果想在多个文件分享变量,必须
定义global对象的属性 global.waring=true)
2、模块可以多次加载,即只在第一次加载时运行一次,后续加载直接读取缓存,清除缓存则模块重新运行
3、模块的加载顺序即为在代码中出现的顺序
CommonJS主要是浏览器端js的模块化(同步加载),在服务器端文件都保存在硬盘上,同步加载没有问题,
但是在浏览器端文件需要从服务器端请求获得,那么同步加载就不适用了
三、AMD规范。
非同步加载模块,允许指定回调函数,适用于浏览器环境,通过requireJS实现
通过 define 定义模块,通过 require 加载模块
define(id?, [dependencies?], callback)
require([module], callback)
使用requirejs可以在浏览器中并行加载多个模块,但是必须提前加载所有依赖
三、CMD规范
实现js库为 sea.js
实现了浏览器的异步加载,可以实现延迟加载(依赖就近)
define(function(require, exports, module) {
var $ = require('jquery');
// $(...)
module.exports = ...
})
AMD和CMD区别:
AMD是对依赖的模块提前执行,CMD是延迟执行,前者推崇依赖前置,后者提倡依赖就近,即用到某模块时
再require,如上,用到 jquery 时,才require('jquery')
四、ES模块化
通过 import 引入,通过 export 导出,由于部分浏览器暂不支持,需要使用 babel 进行编译
14、SPA单页面
前后端分离,初次加载耗时多,单页应用在一个页面展示所有内容,对于SEO有天然弱势,不适用于浏览器的
前进后退逻辑,页面切换需要自己建立堆栈逻辑
18、
<script>
const num = {
a: 10,
add() {
return this.a + 2;
},
reduce: () => this.a - 2
}
console.log(num.add());
console.log(num.reduce());
</script>
// 输出:12 NaN
解析:对于箭头函数来说,this关键字指向它所在的上下文环境(定义时的位置),num定义时的环境,this.a 为 undefined,最后输出 NaN
19、call、bind
<script>
const person = { name: 'tom' };
function sayHi(age) {
return `${this.name} is ${age}`
}
console.log(sayHi.call(person, 5));
console.log(sayHi.bind(person, 5));
</script>
// 输出:tom is 5
// ƒ sayHi(age) {
// return `${this.name} is ${age}`
// }
解析:bind返回绑定上下文环境的函数的拷贝值,不会立即执行
20、['1', '2', '3'].map(parseInt)
['1', '2', '3'].map(parseInt)
// [1, NaN, NaN]
解析: parseInt(string,radix) 解析一个字符串并返回指定基数的十进制整数,
radix 是2-36之间的整数,表示被解析字符串的基数。如果省略或者为0,则按10进制进行解析。如果string参数的第一个字符不属于radix指定进制下的字符,则返回NaN
parseInt('1', 0)、parseInt('2', 1)、parseInt('3', 2)
21、[typeof null, null instanceof Object]
[typeof null, null instanceof Object]
// 输出:["object", false]
解析:typeof返回列表
console.log(typeof undefined) // undefined
console.log(typeof null) // object
console.log(typeof String) // function
console.log(typeof Boolean) // function
console.log(typeof Number) // function
console.log(typeof Symbol) // function
console.log(typeof Object) // function
var a = null;
console.log(typeof a); // object
var a = undefined;
console.log(typeof a); // undefined
var a = '1234';
console.log(typeof a); // string
var a = false;
console.log(typeof a); // boolean
var a = 1;
console.log(typeof a); // number
var a = Symbol(3.14);
console.log(typeof a); // symbol
var a = {};
console.log(typeof a); // object
instanceof运算符用来检测 constructor.prototype 是否存在于参数object的原型链上
22、
<script>
function f() {}
const a = f.prototype, b = Object.getPrototypeOf(f);
console.log(a === b);
</script>
// 输出:false
解析:f.prototype是使用new关键字创建的 f 实例的原型,而Object.getPrototypeOf(f)是 f 函数的原型
a === Object.getPrototypeOf(new f()) // true
b === Function.prototype // true
23
<script>
function showCase(value) {
switch(value) {
case 'A':
console.log('Case A');
break;
case 'B':
console.log('Case B');
break;
case undefined:
console.log('undefined');
break;
default:
console.log('Do not know!');
}
}
showCase(new String('A'));
</script>
// 输出:Do not know!
解析:switch 是严格比较,String实例和string字符串不一样
var str1 = '1234';
var str2 = new String('1234');
console.log(typeof str1); // string
console.log(typeof str2); // object
24
<script>
console.log([2, 1, 0].reduce(Math.pow));
console.log([].reduce(Math.pow));
</script>
// 输出:1 Uncaught TypeError: Reduce of empty array with no initial value
解析:Math.pow(2, 1) => 2 Math.pow(2, 0) => 1
25
<script>
function test() {
var a = 1;
return function() {
eval("");
}
}
test()
</script>
变量a会被GC(垃圾回收)吗?
不会,应为 eval 会欺骗词法作用域,例如function() {eval("var a = 1234")} 创建了一个变量,不确定
eval是否对a进行了引用,所以为了保险,不对其进行优化。try catch、with也不会被回收,with会创建新的
作用域。
26
<script>
const value = 'Value is ' + !!Number(['0']) ? 'test' : 'undefined';
console.log(value);
</script>
// 输出:test
解析:+ 的优先级大于 ?
'Value is false' ? 'test' : 'undefined'
27
<script>
var arr = [0, 1];
arr[5] = 5;
var newArr = arr.filter(x => {
return x === undefined;
})
console.log(newArr.length);
</script>
// 输出: 0
解析:
28、宏任务微任务补充(可与第一题比较理解)
<script>
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
</script>
// 输出:script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
解析:
1、首先,事件循环从宏任务(macrotask)队列开始,这个时候,宏队列中只有script(整体代码)任务;
从宏任务队列中取一个任务出来执行。
a.首先执行console.log('script start'),输出 'script start'。
b.遇到setTimeout把console.log('setTimeout')放到macroTask队列中。
c.执行async1(),输出'async1 start'和'async2',把console.log('async1 end')放到micro队列中。
d.执行到promise,输出'promise1',把console.log('promise2')放到micro队列中。
e.执行console.log('script end')。输出'script end'
2、macrotask执行完之后执行microtask,把microtask quene里面的microtask全部拿出来一次性执行完,
所以输出 'async1 end' 和 'promise2'
3、开始新一轮事件循环,去除macrotask执行,所以会输出 'setTimeout'
29、如何让(a == 1 && a == 2)成立
var a = {
value: 1,
toString: function() {
return this.value++;
}
}
console.log(a == 1 && a == 2) // true
30、类数组
<script>
const obj = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1);
obj.push(2);
console.log(obj);
</script>
// 输出:Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]
解析:
这个obj中定义了两个key值,分别为splice和push分别对应数组原型中的splice和push方法,因此这个obj可以
调用数组中的push和splice方法,调用对象的push方法,push(1) ,因为此时obj中定义 length 为2,所以从
数组的第三项(下标为2)开始插入,这时已经定义了下标为2和3这两项,所以它会替换第三项,也就是下标为2的
值,两次push执行完毕,key为2和3的属性值分别为1和2。此时输出结果为:
Object(4)[empty × 2, 1, 2, splice: f, push: f],因为只定义了2和3两项,所以前面会是 empty
31
<script>
let a = {n: 1};
let b = a;
a.x = a = {n: 2};
console.log(a.x);
console.log(b.x);
</script>
// 输出:undefined {n: 2}
- '.'的优先级大于等号的优先级
- 对象以指针的形式进行存储,每个新对象都是一份新的存储地址
解析:
先执行 ‘.’,所以此时 a 和 b 都是 { n: 1, x: undefined }
'=' 从右向左执行,a = { n: 2 },此时a指向 { n: 2 }
然后执行 a.x = a,由于 a.x 是最开始执行的,已是 { n: 1, x: undefined },不是开始的那个旧的a,而b的地址和旧的a地址一样
32
<script>
var a1 = {}, b1 = '123', c1 = '123';
a1[b1] = 'b';
a1[c1] = 'c';
console.log(a1[b1]);
var a2 = {}, b2 = Symbol('123'), c2 = Symbol('123');
a2[b2] = 'b';
a2[c2] = 'c';
console.log(a2[b2]);
var a3 = {}, b3 = {key: '123'}, c3 = {key: '456'};
a3[b3] = 'b';
a3[c3] = 'c';
console.log(a3[b3]);
</script>
// 输出:c b c
- 对象的键名只能是字符串或 Symbol 类型
- 其它类型的键名会被转化为字符串类型
- 对象转字符串会默认调用 toString 方法
解析:任何 Symbol 类型的值都是不相等的,对象类型的键值会被转为 "[object Object]"
另:Objects和Map类似,都是键值对存储数据
- Map键值可以是任意值,Object只能是字符串或 Symbol
- Map中的键值是有序的,遍历时是按插入的顺序
33
<script>
function Foo() {
Foo.a = function() {
console.log(1);
}
this.a = function() {
console.log(2);
}
}
Foo.prototype.a = function() {
console.log(3);
}
Foo.a = function() {
console.log(4);
}
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();
</script>
// 输出:4 2 1
解析:
Foo.a() 调用 Foo 函数的静态方法a,Foo内部有优先级更高的属性方法,但 Foo 没有被调用, new Foo() 返回函数实例对象,Foo函数内部的属性方法初始化,原型方法建立。
obj.a()调用 obj 实例上的方法 a,其内部有属性方法a,原型方法a,重名时,原型方法优先级高
Foo.a()同上
34
<script>
function user(obj) {
obj.name = 'Hello';
obj = new Object();
obj.name = 'World!';
}
let person = new Object();
user(person);
console.log(person.name); // Hello
</script>
// 输出:Hello
// obj = new Object() 将obj指向了一个新的对象
35
<script>
let x, y;
try {
throw new Error();
} catch (x) {
x = 1;
y = 2;
console.log(x);
}
console.log(x);
console.log(y);
</script>
// 输出: 1 undefined 2
解析:catch 块接收参数 x,当我们传递参数时,这与变量的 x 不同。这个变量 x 是属于 catch 作用域的。 需要注意的是 catch 的作用域,其实并不是常见的块作用域,并不能绑定自己的内部声明的变量。 catch创建的块作用域,只对catch的参数有效。对于在内部声明的变量,catch并没有创建一个新的 作用域,只是一个普通的代码块。
36、(与33题结合理解)
<script>
function fn() {
getValue = function () { console.log(1); };
return this;
}
fn.getValue = function () { console.log(2) }
fn.prototype.getValue = function () { console.log(3) }
var getValue = function () { console.log(4) }
function getValue() { console.log(5) }
getValue();
fn().getValue();
getValue();
new fn.getValue();
new fn().getValue();
</script>
// 输出:4 1 1 2 3
解析:
- 变量提升,var getValue 覆盖了function getValue,所以输出 4
- fn()执行,内部的 getValue又一次覆盖,返回this,this只有在函数执行时在能确定,直接调用方式,this指向window,然后fn().getValue() 得到1(覆盖之后的)
- 输出1,(两次覆盖)
- 点的优先级高于new,new (fn.getValue()) ,输出2
- 输出3
37
<script>
let length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function (fn) {
fn();
arguments[0]();
},
fn: function() {
console.log(this.length);
}
};
obj.method(fn, 1);
obj.fn()
</script>
// 输出: 0 2 5
解析:**任意函数里如果嵌套了非箭头函数,那这个时候嵌套函数里的this在未指定的情况下,应该指向的是window 对象,**所以这里执行fn会打印window.length, let 声明的变量会形成块级作用域,且不存在声明提升。则 length属性并没有添加到window对象中。
argument0 在方法调用中,执行函数体的时候,作为属性访问主体的对象和数组便是其调用方法内this的指向。 (通俗的说,调用谁的方法,this指向谁)
38
<script>
var a = 10;
var foo = {
a: 20,
bar: function () {
var a = 30;
return this.a;
}
}
console.log(foo.bar());
console.log((foo.bar)());
console.log((foo.bar = foo.bar)());
console.log((foo.bar, foo.bar)());
</script>
// 输出:20 20 10 10
解析:
- foo.bar(),foo调用,this指向foo,输出20
- 括号的作用只是改变表达式的运算顺序,输出20
- 重新给foo.bar定义,即 foo.bar = function() { var a = 30; return this.a },此时foo.bar是在window作用域下,输出10
- 逗号运算符,求解过程:先计算表达式1的值,在计算表达式2的值...计算表达式n的值,最后逗号运算符的返回值是表达式n的值。经过逗号运算符之后,就是纯粹的函数了,不再是对象方法的引用,所以此时this指向window,输出10
知识点:
- 默认绑定:独立函数调用时,this指向全局对象window,严格模式下,全局对象无法使用默认绑定,this绑定至undefined
- 隐式绑定:函数this指向调用者(隐式指向),obj.foo() this ->obj、obj1.obj2.foo() this -> obj2
- 隐式丢失:指的是函数中this丢失绑定对象的情况,即应用默认绑定规则,从而将this绑定到window或undefined上,以下情况会发生隐式丢失:
(1)绑定至上下文对象的函数被赋给一个新函数,然后调用这个新函数时
(2)传入回调函数时
- 显示绑定:call()、apply()和bind()
- new绑定:构造函数的this是new之后的新对象(构造器)
39
<script>
function getName() {
for(let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, i * 1000)
}
return
{
name: 'whl'
}
}
console.log(getName());
</script>
// 输出: undefined 0 1 2 3 4
// 数字的输出间隔1秒,改成var则每间隔1秒输出5(五次)
解析:return后面换行了
40、const num = parseInt('2*4', 10);
<script>
const num = parseInt('2*4', 10);
console.log(num);
</script>
// 输出:2
41、Object.defineProperty
<script>
const company = { name: 'Hello' };
Object.defineProperty(company, 'address', { value: 'World' });
console.log(company);
console.log(Object.keys(company));
</script>
// 输出:{ name: 'Hello', address: 'World' } ['name']
解析:Object.defineProperty ()直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。新属性默认不可枚举,修改,可通过以下方式修改:
Object.defineProperty(company, 'address', {
value: 'World',
configurable: true, // 该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
enumerable: true, // 可枚举
writable; true, // 可以被赋值运算符改变
})
42、自增运算符
<script>
let num = 10;
const increaseNumber = () => num++;
const increasePassedNumber = number => number++;
const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);
console.log(num1);
console.log(num2);
console.log(num);
</script>
// 输出:10 10 11
解析:++先返回操作数,再执行累加值
43
<script>
const value = { number: 10 };
const multiply = (x = {...value}) => {
console.log(x.number *= 2);
};
multiply(); // 20
multiply(); // 20
multiply(value); // 20
multiply(value); // 40
</script>
44
<script>
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
</script>
// 输出:1, 2 undfined, 3 undefined, 4
解析:无返回值,默认返回 ‘undefined’,如果改为以下形式:
<script>
let sum = [1, 2, 3, 4].reduce((x, y) => {
console.log(x, y)
return x + y;
});
console.log('sum: ', sum);
// 1, 2
// 3, 3
// 6, 4
// sum: 10
</script>
45
// index.js
console.log('running index.js');
import { sum } from './sum.js';
console.log(sum(1, 2));
// sum.js
console.log('running sum.js');
export const sum = (a, b) => a + b;
// 输出:running sum.js running index.js 3
解析:import是编译阶段执行,因此被导入的模块会先执行,导入模块的文件会后执行。
这是CommonJS中require()和import之间的区别,使用require(),可以在运行代码时根据需要加载依赖项,如果使用require,则会依次打印:running index.js running sum.js 3
46
<script>
function addToList(item, list) {
return list.push(item);
}
const result = addToList('Hello', ['World']);
console.log(result);
</script>
// 输出: 2
解析:push() 方法返回新数组的长度,如果想返回新数组,可将addToList()改为:
function addToList(item, list) {
list.push(item);
return list
}
47
<script>
var a = 0;
if (true) {
a = 10;
console.log(a, window.a);
function a() {};
console.log(a, window.a);
a = 20;
console.log(a, window.a);
}
console.log(a);
</script>
// 输出
// 10 0
// 10 10
// 20 10
// 10
48
<script>
var obj = { x: 1, y: 2, z: 3 }
obj[Symbol.iterator] = function* () {
yield 'x';
yield 'y';
yield 'z';
}
console.log([...obj]);
</script>
参考:developer.mozilla.org/zh-CN/docs/…
49、完成safeGet函数
<script>
function safeGet(obj, str) {
// ...
}
var data = {a: {b: {c: 'hello'}}}
safeGet(data, 'a.b.c') // hello
safeGet(data, 'a.b.c.d') // undefined
safeGet(data, 'a.b.c.d.e.f.g') // undefined
</script>
参考:
(1)
function safeGet(obj, str) {
var keys = str.split('.');
try {
for (var i = 0; i < keys.length; i++) {
obj = obj[keys[i]]
}
return obj;
} catch (e) {
return undefined
}
}
(2)
function safeGet(obj, str) {
try {
return str.split('.').reduce((o, k) => o[k], obj)
} catch (e) {
return undefined;
}
}
50、写一个isPrime()函数
function isPrime(number) {
if (typeof number !== 'number' || !Number.isInteger(number)) {
return false;
}
if (number < 2) {
return false;
}
if (number === 2) {
return true;
} else if (number % 2 === 0) {
return false;
}
let num = Math.sqrt(number);
for (let i = 3; i <= num; i+=2) {
if (number % i === 0) {
return false;
}
}
return true;
}