把知识串一串,连成线,造成体系,今后走上大神之路啦,道路可能会曲折一点,可是咸鱼也要翻一翻身撒~前端
何为变量提高?java
在JavaScript中,函数及变量的声明都将被提高到函数的最顶部 (函数声明的优先级高于变量声明的优先级)
这样就形成了一种不一样于其余语言的现象,初看甚至以为有些诡异:变量能够先使用再声明。举个栗子:node
x = 1; console.log(x); // 1 var x;
var name = 'World!'; (function () { if (typeof name === 'undefined') { var name = 'Jack'; console.log('Goodbye ' + name); } else { console.log('Hello ' + name); } })(); // 输出为 Goodbye Jack
为何会出现这样状况呢?面试
在JavaScript中,变量声明与赋值的分离,如 var a = 2 这个代码是分两步进行的,编译阶段之行变量声明 var a,在执行阶段进行赋值 a = 2,因而便形成了了变量声明提早状况的发生。
解析:对于第二个例子,因为存在变量提高,因此变量声明先于if判断,因此此时 name = undefined,因而便输出了 Goodbye Jack正则表达式
前段时间,前端各大博客被一道题刷屏了编程
++[[]][+[]]+[+[]]==10?
这道题怎么去解决呢,这就涉及到了JS的隐式转换相关的知识了。数组
对于原始类型:Undefined、Null、Boolean、Number、Stringpromise
1,加号运算符(+):若后面的是数字,会直接相加得出结果,如 1 + 1 = 2;若后面的是字符类型,则会进行字符拼接,如 1 + '1' = '11'。
2,减号运算符(-):若后面的是数字,会直接相减得出结果;若后面的字符,则会将其转为数字类型,而后相减得出结果。
3,==运算负责:浏览器
对于对象类型:Object
当对象与一个非对象进行比较等操做时,须要先将其转化为原始类型:首先调用 valueOf(),若结果是原始类型,则返回结果;若结果不是原始类型,则继续调用toSring(),返回其结果,若结果依然不是原始类型,则会抛出一个类型错误。闭包
这里有一道很火的面试题,就是利用对象的类型转换原理:
a == 1 && a == 2 && a == 3 //答案: var a = {num : 0}; a.valueOf = function() { return ++a.num; }
以上大概为基础的隐式转换规则,可能不太完善,欢迎你们留言补充。好,有了这些准备后,让咱们再来看下一开始的题目,让咱们来逐步拆解:
1,根据运算符的优先级,咱们能够获得:(++[[]][+[]])+[+[]] 2,根据隐式转换,咱们获得:(++[[]][0])+[0] 3,再次简化:(++[]) + [0] 4,这个时候就很明朗了,最终划为字符拼接 '1' + '0' = '10';
简单的讲,闭包就是指有权访问另外一个函数做用域中的变量的函数。MDN 上面这么说:闭包是一种特殊的对象,是函数和声明该函数的词法环境的组合。
function func() { var a = 1; return function fn() { console.log(a); } } func()(); // 1
这里函数func在调用后,其做用域并无被销毁,依然能够被函数fn访问,因此输出为1。
这里有道很经典的面试题
function fun(n,o){ console.log(o); return { fun: function(m){ return fun(m,n); } }; } var a = fun(0); // ? a.fun(1); // ? a.fun(2); // ? a.fun(3); // ? var b = fun(0).fun(1).fun(2).fun(3); // ? var c = fun(0).fun(1); // ? c.fun(2); // ? c.fun(3); // ?
undefined
0
0
0
undefined, 0, 1, 2
undefined, 0
1
1
哈哈,有点绕,有兴趣的同窗能够简单看下。
在实际开发或面试中,咱们常常会碰到克隆的问题,这里咱们简单的总结下。
浅克隆就是复制对象的引用,复制后的对象指向的都是同一个对象的引用,彼此之间的操做会互相影响
var a = [1,2,3]; var b = a; b[3] = 4; console.log(a, b); // [1,2,3,4] [1,2,3,4]
实际开发中,若须要同步对象的变化,每每用的就是浅克隆,直接复制对象引用便可。
开发过程当中,咱们每每须要断开对象引用,不影响原对象,这个时候咱们就用到深克隆了,有以下方法:
方法一
JSON.parse(JSON.stringify()),对于大多数状况均可以用这种方法解决,一步到位。可是若对象中存在正则表达式类型、函数类型等的话,会出现问题:会直接丢失相应的值,同时若是对象中存在循环引用的状况也没法正确处理
let a = {name: '小明'}; let b = JSON.parse(JSON.stringify(a)); b.age = 18; console.log(a, b); // {name: '小明'} {name: "小明", age: 18}
方法二
对于数组,咱们能够利用Array的slice和concat方法来实现深克隆
let a = [1,2,3]; let b = a.slice(); b.push(4); console.log(a, b); // [1,2,3] [1,2,3,4] let a1 = [1,2,3]; let b1 = a.concat(4); b1.push(5); console.log(a, b); // [1,2,3] [1,2,3,4,5]
方法三
jQuery中的extend复制方法:$.extend(true, target, obj)
let a = {name: '小明'}; let b = {} $.extend(true, b, a); b.age = 18; console.log(a, b); // {name: "小明"} {name: "小明", age: 18}
关于this指向的问题,这里是有必定判断方法的:
位置:this其实是在函数被调用时发生的绑定,它指向什么彻底取决于函数在哪里调用
规则:默认绑定、隐式绑定、显式绑定、new绑定
咱们在实际判断的时候,须要将两者结合起来。
var name = '小明'; function print() { console.log(this.name); // '小明' console.log(this); //window对象 } print(); // '小明'
解析:print()直接使用不带任何修饰的函数引用进行的调用,这个时候只能使用默认绑定规则,即this指向全局对象,因此此题输出为:'小明'
function foo() { console.log(this.a) } var obj = { a:2, foo:foo } obj.foo() // 2
解析:当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因此此题的this被绑定到obj,因而this.a和obj.a是同样的。
这里有两点点须要注意:
1,对象属性引用链中只有上一层或者说最后一层在调用位置中起做用,举例以下:
function foo() { console.log(this.a); } var obj2 = { a: 10, foo: foo } var obj1 = { a: 2, obj2: obj2 } obj1.obj2.foo(); // 10
2,隐式丢失:被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上。举例以下:
var a = 'hello world'; function foo(){ console.log(this.a) } var obj = { a:1, foo:foo } var print = obj.foo; print(); // hello world
解析:虽然print是obj.foo的一个引用,可是实际上,它引用的是foo函数自己,因此此时print()实际上是一个不带任何修饰的函数调用,应用了隐式绑定。
利用call(),apply(),bind()强制绑定this指向的咱们称之为显示绑定,举例以下:
function foo() { console.log(this.a); } var obj = { a:1 } foo.call(obj); // 1
这里有一点须要注意:显示绑定依然没法解决上面提到的丢失绑定问题。举例以下:
var a = 'hello world'; function foo(){ console.log(this.a) } var obj = { a:1, foo:foo } var print = obj.foo; print.bind(obj) print(); // hello world
这里有关call、apply、bind的具体用法就再也不一一阐述了,后面的部分会详细讲解。
这是最后一条this的绑定规则,使用new来调用函数,或者说发生构造函数调用时,会执行下面的操做:
这个过程当中发生了this绑定,举例以下:
function Person(name) { this.name = name; } var p = new Person('小明'); console.log(p.name); // 小明
这里再也不一一举例对比优先级,直接给出结论:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定,有兴趣的同窗能够实际比对一下。
常规this指向判断流程:
定义:
使用一个指定的this值和单独给出的一个或多个参数来调用一个函数
语法:
fun.call(thisArg, arg1, arg2, ...)
参数:
thisArg:(1) 不传,或者传null,undefined, 函数中的this指向window对象
(2) 传递另外一个函数的函数名,函数中的this指向这个函数的引用,并不必定是该函数执行时真正的this值
(3) 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean(4)传递一个对象,函数中的this指向这个对象arg1, arg2, ...:指定的参数列表
举例以下:
var obj = {a: '小明'}; function print() { console.log(this); } print.call(obj); // {a: '小明'}
实现call方法:
Function.prototype.selfCall = function(context, ...args) { let fn = this; context || (context = window); if (typeof fn !== 'function') throw new TypeError('this is not function'); let caller = Symbol('caller'); context[caller] = fn; let res = context[caller](...args); delete context[caller]; return res; }
apply()方法与call()方法类似,区别在于:call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。
举例以下:
var arr = [34,5,3,6,54,6,-67,5,7,6,-8,687]; Math.max.apply(Math, arr); // 687 Math.min.call(Math, ...arr); // -67
定义:
bind()方法建立一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表做为原函数的参数序列的前若干项。
语法:
function.bind(thisArg[, arg1[, arg2[, ...]]])
参数:
thisArg:调用绑定函数时做为this参数传递给目标函数的值
arg1, arg2, ...:当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。
举例以下:
function print() { console.log(this); } let obj = {name: '小明'}; let fn = print.bind(obj); fn(); // {name: "小明"}
Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一
方法一:new Promise
// 声明Promise后会当即执行 var promise = new Promise(function(resolve, reject) { resolve('Hello'); }) console.log(promise); // Promise{<resolved>: "Hello"}
方法二:直接建立
var promise = Promise.resolve('Hello'); console.log(promise); // Promise{<resolved>: "Hello"}
promise至关于一个状态机,具备三种状态:
(1) promise 对象初始化状态为 pending
(2) 当调用resolve(成功),会由pending => fulfilled
(3) 当调用reject(失败),会由pending => rejected
注:promsie状态 只能由 pending => fulfilled/rejected, 一旦修改就不能再变
1,Promise.prototype.then()
then() 方法返回一个 Promise 。它最多须要有两个参数:Promise 的成功 (onFulfilled) 和 失败状况 (onRejected) 的回调函数。
举例以下:
var promise = new Promise((resolve, reject) => { // 成功 resolve('hello'); }); promise.then((res) => { console.log(res); // hello return Promise.reject('error'); }).then((success) => { console.log('success', success); }, (err) => { console.log('error', err); // error error });
2,Promise.resolve()
Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。但若是这个值是个thenable(即带有then方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态(指resolved/rejected/pending/settled);若是传入的value自己就是promise对象,则该对象做为Promise.resolve方法的返回值返回;不然以该值为成功状态返回promise对象。
举例以下:
var promise = Promise.resolve('hello'); promise.then((res) => { console.log(res); }); // hello // Promise {<resolved>: undefined}
此时promise的状态为题fulfilled
3,Promise.reject()
Promise.reject(reason)方法返回一个带有拒绝缘由reason参数的Promise对象。
举例以下:
var promise = Promise.reject('error'); promise.then((res) => { console.log('success', res); }, (res) => { console.log('error', res); // error error });
4,Promise.race()
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
举例以下:
var promise1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, 'one'); }); var promise2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, 'two'); }); Promise.race([promise1, promise2]).then(function(value) { console.log(value); // two });
5,Promise.all()
Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内全部的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);若是参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败缘由的是第一个失败 promise 的结果。
举例以下:
var promise1 = Promise.resolve(3); var promise2 = 42; var promise3 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, 'foo'); }); Promise.all([promise1, promise2, promise3]).then(function(values) { console.log(values); // [3, 42, "foo"] });
6,Promise.prototype.finally()
finally() 方法返回一个Promise。在promise结束时,不管结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都须要执行的代码提供了一种方式。
这避免了一样的语句须要在then()和catch()中各写一次的状况。
举例以下:
var promise = Promise.resolve('Hello'); promise.then((res) => { console.log(res); // Hello }).finally((res) => { console.log('finally'); // finally })
7,Promise.prototype.catch()
catch() 方法返回一个Promise,而且处理拒绝的状况,捕获前面then中发送的异常
只要Promsie状态更改成reject或者抛出异常,都会进入catch方法。举例以下:
var promise1 = Promise.reject('Hello'); promise1.then((res) => { console.log('success' + res); }).catch((res) => { console.log('catch ' + res); // catch Hello })
Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是咱们常用异步的原理。
在JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。
宏任务:
微任务:
浏览器中的事件循环机制是什么样子呢?不废话,直接上图:
过程以下:
举例以下:
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');
答案以下:
script start、script end、promise一、promise二、setTimeout
解析:
step1
console.log('script start');
Stack Queue: [console]
Macrotask Queue: []
Microtask Queue: []
打印结果:1
step2
setTimeout(function() { console.log('setTimeout'); }, 0);
setTimeout属于宏任务,因此:
Stack Queue: [setTimeout]
Macrotask Queue: [callback1]
Microtask Queue: []
step3
Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); });
promise属于微任务,因此有:
Stack Queue: [promise]
Macrotask Queue: [callback1]
Microtask Queue: [callback2]
step4
console.log('script end');
同步任务,直接执行
打印结果:script end
step5
遍历微任务队列:Microtask Queue: [callback2],执行其函数
打印顺序依次为:promise一、promise2
step6
微任务队列为空后,遍历宏任务队列:Macrotask Queue: [callback1],执行其回调函数
打印结果:setTimeout
因此最终结果为:script start、script end、promise一、promise二、setTimeout
因为时间比较仓促,本次总结还存在着许多遗漏,如JS原型,node环境下的Event Loop,函数柯里化等,也有许多理解不到位的状况,往后会逐渐完善与补充。
注:若是文章中有不许确的地方,欢迎你们留言交流。😝