JavaScript 常见面试题 你都知道哪些?

159 阅读12分钟

宏任务和微任务

这里有几个注意点

  1. Promise 的任务是同步的 但是后续是异步的

  2. 就是说 then catch finally 都是异步中的微任务

  3. setTimeoutsetImmediate谁先执行

  4. 一般来说,setImmediate会在setTimeout之前执行

  5. 来个加强版的题目

console.log('script start')		1

async function async1() {
  await async2()
  console.log('async1 end')			5
}
async function async2() {
  console.log('async2 end')		2
}
async1()

setTimeout(function() {
  console.log('setTimeout')		8
}, 0)

new Promise(resolve => {
  console.log('Promise')		3
  resolve()
})
  .then(function() {
    console.log('promise1')		6
  })
  .then(function() {
    console.log('promise2')		7
  })

console.log('script end')		4
宏任务(macrotask)微任务(microtask)
谁发起的宿主(Node、浏览器)JS引擎
具体事件1. script (可以理解为外层同步代码) 2. setTimeout/setInterval 3. UI rendering/UI事件 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js)1. Promise 2. MutaionObserver 3. Object.observe(已废弃;Proxy对象替代) 4. process.nextTick(Node.js)
谁先运行后运行先运行
会触发新一轮Tick吗不会

[css] 写的css样式是否能被js所读到?如果可以如何读取?

  • www.cnblogs.com/susufufu/p/…
  • 利用 document.styleSheets属性,返回当前页面的所有StyleSheet对象(即所有样式表),它是一个只读的类数组对象,它的元素是CSSStyleSheet对象(继承自StyleSheet对象)
  • 通过Element对象的getAttribute()、setAttribute()、removeAttribute()直接读写style属性

[html] 怎样计算首屏和白屏的时间?

[js] 解释下什么是暂时性死区?

在es6 有了 let const 声明变量前 变量是不可用的 会暂时生产死区;

console.log(typeof x)
let x;

[html] 如何在页面打开PDF文件

说说你对函数是一等公民的理解

  • 在编程语言中,一等公民可以作为函数参数,可以作为函数返回值,也可以赋值给变量
  • 例如,字符串在几乎所有编程语言中都是一等公民,字符串可以做为函数参数,字符串可以作为函数返回值,字符串也可以赋值给变量。
  • 对于各种编程语言来说,函数就不一定是一等公民了,比如Java 8之前的版本
  • 对于JavaScript来说,函数可以赋值给变量,也可以作为函数参数,还可以作为函数返回值,因此JavaScript中函数是一等公民。

全局DOM变量 HTML标签使用ID属性就会生产一个全局变量

<div id="foo"></div>
HTML元素 利用ID 声明的 HTML元素 会产生一个 全局变量 
if (typeof foo == "undefined") {
 foo = 42; // 永远也不会运行
}
console.log( foo ); // HTML元素

安全的利用隐式转换

我们要对 == 两边的值认真推敲,以下两个原则可以让我们有效地避免出错。

• 如果两边的值中有 true 或者 false,千万不要使用 ==。

• 如果两边的值中有 []、"" 或者 0,尽量不要使用 ==。

为什么 a == b 的结果不是 true ?它们的字符串值相同(同为 "[object Object]"),按道

理应该相等才对?

但是如果 a < b 和 a == b 结果为 false,为什么 a <= b 和 a >= b 的结果会是 true 呢?

因为根据规范 a <= b 被处理为 b < a,然后将结果反转。因为 b < a 的结果是 false,所

以 a <= b 的结果是 true。

var a = { b: 12 }
var b = { b: 13 }
console.log(a < b) // false
console.log(a > b) // false
console.log(a == b) // false
console.log(a <= b) // true
console.log(a >= b) // true

涉及隐式转换的

这里每次调用 new Number() 的时候 其实内部都会调用 Number.prototype.valueOf() 我们重写这个函数就可以达到我们想要的结果 会产生副作用

var i = 2;
Number.prototype.valueOf = function() {
 return i++;
};
var a = new Number( 42 );
if(a == 2  && a==3) {
	console.log('看来你已经理解了');
}

隐式转换的公式

(1) 如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果。

(2) 如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果。

(3) 如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果;

(4) 如果 Type(y) 是布尔类型,则返回 x     == ToNumber(y) 的结果

(5) 如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果;

(6) 如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPromitive(x) == y 的结果。

(7) 如果 x 为 null,y 为 undefined,则结果为 true。

(8) 如果 x 为 undefined,y 为 null,则结果为 true。

令人恶心的 == 隐士转换 让人头晕

"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 晕!
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 晕!
false == ""; // true -- 晕!
false == []; // true -- 晕!
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 晕!
"" == []; // true -- 晕!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 晕!
0 == {}; // false
0 == ''  // true
0 == '\n'  // true
"0" == false; // true -- 晕!
false == 0; // true -- 晕!
false == ""; // true -- 晕!
false == []; // true -- 晕!
"" == 0; // true -- 晕!
"" == []; // true -- 晕!
0 == []; // true -- 晕!
"" == 0; // true -- 晕!
"" == []; // true -- 晕!
0 == []; // true -- 晕!

隐士转换 的 + - 号

    • 号 都走的 ToSrting 会把元素转换成字符串
    • 号 都会走 TONumber 会把元素转换成 Number 类型 进行操作
console.log([3] - [2])   1
console.log([3] + [2])   32

原生JS获取 时间戳 的 几种方法

  1. new Date().getTime()
  2. Date.now()
  3. +new Date() // 利用 一元运算符 进行隐士转换 也叫强制转换    

JSON.stringify() 几种用法

你不知道入参 大多数人 使用 都其实是传入参数 转换字符串 其实这里 有几个注意点

  1. undefined 会丢失
  2. function 会丢失
  3. 入参的 第二个参数可以决定 解析什么 第二个参数可以 传入数组 或者 是函数 或者是null
  4. 数组必须是 可以解析的 字符串数组 也就是 [ 'a','b' ] 数组中的参数必须是字符串
  5. 函数要有返回值
  6. JSON.string 还有一个可选参数 space,用来指定输出的缩进格式。space 为正整数时是指定 每一级缩进的字符数,它还可以是字符串,此时最前面的十个字符被用于每一级的缩进 JSON.stringify(a,null,3)
 如果第二个参数传入数组
 const a = { a: 1,b:2 ,c: [1,2,3]} 
console.log(JSON.stringify(a,['c']))  代表只解析 第一个参数中的 子元素c
 // {"c":[1,2,3]}

 如果第二个参数传入函数
 const a = { a: 1,b:2 ,c: [1,2,3]} 
console.log(JSON.stringify( a, function(k,v){
 if (k !== "c") return v;
} ))
// {"a":1,"b":2}

typeof 防御机制 和 undefined undeclared 的区别

很多开发人员将 undefined 和 undeclared 混为一谈,但在 JavaScript 中它们是两码事。 undefined 是值的一种。undeclared 则表示变量还没有被声明过。 遗憾的是,JavaScript 却将它们混为一谈,在我们试图访问 "undeclared" 变量时这样报 错:ReferenceError: a is not defined,并且 typeof 对 undefined 和 undeclared 变量都返回 "undefined"。

注意暂时性死区 例外;

console.log(typeof x)
let x;
const str;
console.log(str); //  undefined
console.log(str2); //  str2 is not defined    undeclared
console.log(typeof str2); // undefined 不会报错

delete 删除符

delete 删除数组中的单元的时候 不会影响 length 属性

const arr = [1,2,3]
arr.length // 3
delete arr[1]
arr[1]  //  empty
arr,length // 3

toFixed() 的几种用法

看了下面的代码会有人有疑问 为何设置了一个变量 12 就可以使用 tofiexd()

拿住number 类型的 12 去直接 tofixed() 却会报错

因为 . 被视为常量 42. 的一部分(如前所述),所以没有 . 属 性访问运算符来调用 tofixed 方法。

const num = 12;
num.toFixed(2); // 12.00
12.toFixed(2) // Uncaught SyntaxError: Invalid or unexpected token
12..toFixed(2) //12.00
(12).toFixed(2) // 12.00
0.42.toFixed(3) // 0.420
12 .toFixed(2) // 12.00   注意空格

整数检测 Number.isInteger() Number.isSafeInteger()

Number.isInteger(121) // true
Number.isInteget(121.00) // true
Number.isInteget(121.1)  false

Number.isNaN 和 window.isNaN

window.isNaN('a')  true
Number.isNaN('a')  false   // ES6 之后的

Object.is(num1,num2) 判断两个值是否绝对相等

Object.is(1,1)  true
Object.is(1,'1') false  // 内部是 ===

继承的几种方式

www.jb51.net/article/787…    

使用Object.create实现类式继承

function Shape() {
 this.x = 0;
 this.y = 0;
}
 
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info("Shape moved.");
};
 
// Rectangle - subclass
function Rectangle() {
 Shape.call(this); //call super constructor.
}
 
Rectangle.prototype = Object.create(Shape.prototype);
 
var rect = new Rectangle();
 
rect instanceof Rectangle //true.
rect instanceof Shape //true.
 
rect.move(1, 1); //Outputs, "Shape moved."

使用utilities工具包自带的util.inherites

const util = require('util');
const EventEmitter = require('events');
 
function MyStream() {
  EventEmitter.call(this);
}
 
util.inherits(MyStream, EventEmitter);
 
MyStream.prototype.write = function(data) {
  this.emit('data', data);
}
 
var stream = new MyStream();
 
console.log(stream instanceof EventEmitter); // true
console.log(MyStream.super_ === EventEmitter); // true
 
stream.on('data', (data) => {
 console.log(`Received data: "${data}"`);
})
stream.write('It works!'); // Received data: "It works!"


// 源码实现
exports.inherits = function(ctor, superCtor) {
 
 if (ctor === undefined || ctor === null)
  throw new TypeError('The constructor to "inherits" must not be ' +
            'null or undefined');
 
 if (superCtor === undefined || superCtor === null)
  throw new TypeError('The super constructor to "inherits" must not ' +
            'be null or undefined');
 
 if (superCtor.prototype === undefined)
  throw new TypeError('The super constructor to "inherits" must ' +
            'have a prototype');
 
 ctor.super_ = superCtor;
 Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
};

其中Object.setPrototypeOf即为ES6新特性,将一个指定的对象的原型设置为另一个对象或者null

语法

Object.setPrototypeOf(obj, prototype)
obj为将要被设置原型的一个对象

prototype为obj新的原型(可以是一个对象或者null).

如果设置成null,即为如下示例

Object.setPrototypeOf({}, null);

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
 obj.__proto__ = proto;
 return obj; 
}

使用extends关键字

class Polygon {
 constructor(height, width) {
  this.name = 'Polygon';
  this.height = height;
  this.width = width;
 }
}
 
class Square extends Polygon {
 constructor(length) {
  super(length, length);
  this.name = 'Square';
 }
}

对象关联 Object.create()

Object.create(..) 会创建一个新对象(bar)并把它关联到我们指定的对象(foo),这样 我们就可以充分发挥 [[Prototype]] 机制的威力(委托)并且避免不必要的麻烦(比如使 用 new 的构造函数调用会生成 .prototype 和 .constructor 引用)。

Object.create(null) 会 创 建 一 个 拥 有 空( 或 者 说 null)[[Prototype]] 链接的对象,这个对象无法进行委托。由于这个对象没有原型链,所以 instanceof 操作符(之前解释过)无法进行判断,因此总是会返回 false。 这些特殊的空 [[Prototype]] 对象通常被称作“字典”,它们完全不会受到原 型链的干扰,因此非常适合用来存储数据。

const foo = {
	hello: () => {
  	console.log('hello world');
  }
}

const newObj = Object.create(foo);

newObj.hello(); // heelo world

Object.create() polyfill 代码

Object.create(..) 是在 ES5 中新增的函数,所以在 ES5 之前的环境中(比如旧 IE)如 果要支持这个功能的话就需要使用一段简单的 polyfill 代码,它部分实现了 Object. create(..) 的功能:

if (!Object.create) {
  Object.create = function(o) {
    function F(){}
    F.prototype = o;
    return new F();
	};
}

Object.create() 第二个参数 可以往第一个参数里面追加值 但是需要通过 defineProperty 写法

var anotherObject = {
  a:2
};
var myObject = Object.create( anotherObject, {
b: {
  enumerable: false,
  writable: true,
  configurable: false,
  value: 3
},
c: {
  enumerable: true,
  writable: false,
  configurable: false,
  value: 4
}
});

对象检查是否存在属性

in 检查符 直接追溯到Object 的原型链

const myObject = {a : 2};
"a" in myObject // true
"b" in myObject // false

hasOwnProperty 检查符 只会检查对象中是否存在

const myObject = {a : 2};
myObject.hasOwnProperty("a") // true
myObject.hasOwnProperty("b") // false

属性描述符

ES5开始 所有属性都具备了 属性描述符

const myObject = {
  a : 2,
};
Object.getOwnPropertyDescriptor(myObject, "a");
// value: 2 ,
// writable :true  是否可修改
// enumerable: true  是否可枚举
// configurable : true  属性是否可配置

在创建普通属性 时候 属性描述符 会使用默认值 都是true

也可以使用 Object.defineProperty(...) 来添加新属性 或者修改一个已有属性

Object.defineProperty(myObject,'a', {
	value : 4,
  writable: true,
  enumerable: true,
  configurable: true
})

myObject.a; // 4

Writable writable 决定是否可以修改属性的值

var myObject = {};
Object.defineProperty( myObject, "a", {
  value: 2,
  writable: false, // 不可写!
  configurable: true,
  enumerable: true
} );
myObject.a = 3;
myObject.a; // 2

如你所见,我们对于属性值的修改静默失败(silently failed)了。如果在严格模式下,这 种方法会出错

Configurable 只要属性是可配置的,就可以使用 defineProperty(..) 方法来修改属性描述符:

var myObject = {
	a:2
};
myObject.a = 3;
myObject.a; // 3
Object.defineProperty( myObject, "a", {
  value: 4,
  writable: true,
  configurable: false, // 不可配置!
  enumerable: true
});
myObject.a; // 4
myObject.a = 5;
myObject.a; // 5
Object.defineProperty( myObject, "a", {
  value: 6,
  writable: true,
  configurable: true,
  enumerable: true
} ); // TypeError
// 最后一个 defineProperty(..) 会产生一个 TypeError 错误,不管是不是处于严格模式,尝
// 试修改一个不可配置的属性描述符都会出错。注意:如你所见,把 configurable 修改成
// false 是单向操作,无法撤销
// 要注意有一个小小的例外:即便属性是 configurable:false,我们还是可以
// 把 writable 的状态由 true 改为 false,但是无法由 false 改为 true。
// 除了无法修改,configurable:false 还会禁止删除这个属性
delete myObject.a ; // 没有作用

Enumerable

从名字就可以看出,这个描述符控制的是属性是否会出现在对象的属性枚举中,比如说 for..in 循环。如果把 enumerable 设置成 false,这个属性就不会出现在枚举中,虽然仍 然可以正常访问它。相对地,设置成 true 就会让它出现在枚举中

对象常量

结合 writable:false 和 configurable:false 就可以创建一个真正的常量属性(不可修改、 重定义或者删除)

var myObject = {};
Object.defineProperty( myObject, "FAVORITE_NUMBER", {
  value: 42,
  writable: false,
  configurable: false
} );

禁止扩展 对象

如 果 你 想 禁 止 一 个 对 象 添 加 新 属 性 并 且 保 留 已 有 属 性, 可 以 使 用 Object.prevent Extensions(..):

var myObject = {
	a:2
};
Object.preventExtensions( myObject );
myObject.b = 3;
myObject.b; // undefined	

密封

Object.seal(..) 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(..) 并把所有现有属性标记为 configurable:false。 所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以 修改属性的值)。

const myObject = {a:2};
Object.seal(myObject)
delete myObject.a;
myObject.a; // 2

冻结

Object.freeze(..) 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal(..) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们 的值。

这个方法是你可以应用在对象上的级别最高的不可变性,它会禁止对于对象本身及其任意 直接属性的修改(不过就像我们之前说过的,这个对象引用的其他对象是不受影响的)。

你可以“深度冻结”一个对象,具体方法为,首先在这个对象上调用 Object.freeze(..), 然后遍历它引用的所有对象并在这些对象上调用 Object.freeze(..)。但是一定要小心,因 为这样做有可能会在无意中冻结其他(共享)对象。

const obj = {
  prop: 42
};
Object.freeze(obj);
obj.prop = 33;
// Throws an error in strict mode
console.log(obj.prop);
// expected output: 42

匿名函数的缺点

匿名函数表达式书写起来简单快捷,很多库和工具也倾向鼓励使用这种风格的代码。但是它也有几个缺点需要考虑。

1.匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难。

2.如果没有函数名,当函数需要引用自身时只能使用已经过期的argunents.callee 引用,

比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑自身。

3.匿名函数省略了对于代码可读性/可理解性很重要的函数名。一个描述性的名称可以让

代码不言自明。

setTimeout(() => {
 // 匿名函数
})

行内函数表达式 强大且没有匿名函数的缺点

setTimeout(function myHandle(){
	// 我有名字  了  
})

IIFE 函数 (立即执行函数)

const a = 2;
(function IIFE(global)){
  const a = 3;
 	console.log(a); // 3
	console.log(global.a); //2
 }(window);
console.log(a); // 2

undefined (挖坑赋值)

undefined = true; 
(function IIFE(undefined) {
	var a ;
  if(a === undefined) {
  	console.log('undefiend is safe here !');
  }
})();