宏任务和微任务
这里有几个注意点
-
Promise 的任务是同步的 但是后续是异步的
-
就是说 then catch finally 都是异步中的微任务
-
setTimeout,setImmediate谁先执行 -
一般来说,
setImmediate会在setTimeout之前执行 -
来个加强版的题目
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获取 时间戳 的 几种方法
- new Date().getTime()
- Date.now()
- +new Date() // 利用 一元运算符 进行隐士转换 也叫强制转换
JSON.stringify() 几种用法
你不知道入参 大多数人 使用 都其实是传入参数 转换字符串 其实这里 有几个注意点
- undefined 会丢失
- function 会丢失
- 入参的 第二个参数可以决定 解析什么 第二个参数可以 传入数组 或者 是函数 或者是null
- 数组必须是 可以解析的 字符串数组 也就是 [ 'a','b' ] 数组中的参数必须是字符串
- 函数要有返回值
- 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 // 内部是 ===
继承的几种方式
使用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 !');
}
})();