JavaScript——基础篇

把知识串一串,连成线,造成体系,今后走上大神之路啦,道路可能会曲折一点,可是咸鱼也要翻一翻身撒~前端

1、变量提高

何为变量提高?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正则表达式

2、隐式转换

前段时间,前端各大博客被一道题刷屏了编程

++[[]][+[]]+[+[]]==10?

这道题怎么去解决呢,这就涉及到了JS的隐式转换相关的知识了。数组

简述隐式转换规则

对于原始类型:Undefined、Null、Boolean、Number、Stringpromise

1,加号运算符(+):若后面的是数字,会直接相加得出结果,如 1 + 1 = 2;若后面的是字符类型,则会进行字符拼接,如 1 + '1' = '11'。
2,减号运算符(-):若后面的是数字,会直接相减得出结果;若后面的字符,则会将其转为数字类型,而后相减得出结果。
3,==运算负责:浏览器

  • undefined == null,结果为true
  • String == Boolean,须要将两个操做数同时转化为Number
  • String/Boolean == Number,须要将 String/Boolean 转为 Number

对于对象类型: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}

5、this指向

关于this指向的问题,这里是有必定判断方法的:

位置:this其实是在函数被调用时发生的绑定,它指向什么彻底取决于函数在哪里调用
规则:默认绑定、隐式绑定、显式绑定、new绑定

咱们在实际判断的时候,须要将两者结合起来。

1,默认规则

var name = '小明';
function print() {
    console.log(this.name);  // '小明'
    console.log(this);   //window对象
}
print(); 
// '小明'

解析:print()直接使用不带任何修饰的函数引用进行的调用,这个时候只能使用默认绑定规则,即this指向全局对象,因此此题输出为:'小明'

2,隐式绑定

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()实际上是一个不带任何修饰的函数调用,应用了隐式绑定。

3,显示绑定

利用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的具体用法就再也不一一阐述了,后面的部分会详细讲解。

4,new绑定

这是最后一条this的绑定规则,使用new来调用函数,或者说发生构造函数调用时,会执行下面的操做:

  • 建立(或者说构造)一个全新的对象
  • 这个新对象会被执行[[Prototype]]链接
  • 这个新对象会绑定到函数调用的this
  • 若是函数没有返回其余对象,那么new表达式中的函数调用会自动返回这个新对象。

这个过程当中发生了this绑定,举例以下:

function Person(name) {
    this.name = name;
}
var p = new Person('小明');
console.log(p.name);    // 小明

5,优先级

这里再也不一一举例对比优先级,直接给出结论:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定,有兴趣的同窗能够实际比对一下。
常规this指向判断流程:

  • 函数是否在new中调用(new绑定) ? 若是是的话this绑定的就是新建立的对象
  • 函数是否经过call、apply、bind(显示绑定) ? 若是是的话,this绑定的是指定的对象
  • 函数是否在某个上下文对象中被调用(隐时绑定) ? 若是是的话,this绑定的是那个上下文对象
  • 若是都不是的话,使用默认绑定

6、call、apply、bind

1,call()

定义:

使用一个指定的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;
}

2,apply()

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

3,bind()

定义:

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: "小明"}

7、Promise

1,什么是Promise?

Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一

2,建立Promise

方法一: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"}

3,Promise状态

promise至关于一个状态机,具备三种状态:

  • pending
  • fulfilled
  • rejected

(1) promise 对象初始化状态为 pending

(2) 当调用resolve(成功),会由pending => fulfilled

(3) 当调用reject(失败),会由pending => rejected

注:promsie状态 只能由 pending => fulfilled/rejected, 一旦修改就不能再变

4,Promise API

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
})

8、Event Loop

1,前言

Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是咱们常用异步的原理。

2,宏任务与微任务

在JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。

宏任务:

  • script所有代码
  • setTimeout
  • setInterval
  • setImmediate (Node独有)
  • I/O
  • UI rendering (浏览器独有)

微任务:

  • process.nextTick (Node独有)
  • Promise
  • Object.observe
  • MutationObserver

3,浏览器的Event Loop

浏览器中的事件循环机制是什么样子呢?不废话,直接上图:
图片描述

图片描述

过程以下:

  • 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(好比setTimeout等);
  • 全局Script代码执行完毕后,调用栈Stack会清空;
  • 检查微任务队列是否为空,若不为空,则取出位于队首的回调任务,放入调用栈Stack中执行,队列长度减1。如此循环往复,直至微任务队列为空
  • 微任务队列为空后,检查宏任务队列是否为空,若不为空,则取出宏队列中位于队首的任务,放入Stack中执行,队列长度减1。如此循环往复,直至宏任务队列为空

举例以下:

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

9、总结

因为时间比较仓促,本次总结还存在着许多遗漏,如JS原型,node环境下的Event Loop,函数柯里化等,也有许多理解不到位的状况,往后会逐渐完善与补充。

注:若是文章中有不许确的地方,欢迎你们留言交流。😝
相关文章
相关标签/搜索