JavaScript——基础篇

栏目: JavaScript · 发布时间: 4年前

内容简介:把知识串一串,连成线,形成体系,从此走上大神之路啦,道路可能会曲折一点,但是咸鱼也要翻一翻身撒~何为变量提升?在JavaScript中,函数及变量的声明都将被提升到函数的最顶部 (函数声明的优先级高于变量声明的优先级)

把知识串一串,连成线,形成体系,从此走上大神之路啦,道路可能会曲折一点,但是咸鱼也要翻一翻身撒~

一、变量提升

何为变量提升?

在JavaScript中,函数及变量的声明都将被提升到函数的最顶部 (函数声明的优先级高于变量声明的优先级)

这样就造成了一种不同于其他语言的现象,初看甚至觉得有些诡异:变量可以先使用再声明。举个栗子:

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、String

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}

五、this指向

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

位置:this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里调用

我们在实际判断的时候,需要将二者结合起来。

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绑定的是那个上下文对象
  • 如果都不是的话,使用默认绑定

六、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参数传递给目标函数的值

举例如下:

function print() {
    console.log(this);
}

let obj = {name: '小明'};
let fn = print.bind(obj);

fn();    // {name: "小明"}

七、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是否成功完成后都需要执行的代码提供了一种方式。

举例如下:

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

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

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

JavaScript——基础篇

JavaScript——基础篇

过程如下:

  • 执行全局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、promise1、promise2、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],执行其函数

打印顺序依次为:promise1、promise2

step6

微任务队列为空后,遍历宏任务队列:Macrotask Queue: [callback1],执行其回调函数

打印结果:setTimeout

所以最终结果为:script start、script end、promise1、promise2、setTimeout

九、总结

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

注:如果文章中有不准确的地方,欢迎大家留言交流。:stuck_out_tongue_closed_eyes:

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编程的修炼(中英双语)

编程的修炼(中英双语)

[荷]Edsger W. Dijkstra / 裘宗燕 / 电子工业出版社 / 2013-7 / 79.00元

本书是图灵奖获得者Edsger W. Dijkstra在编程领域里的经典著作中的经典。作者基于其敏锐的洞察力和长期的实际编程经验,对基本顺序程序的描述和开发中的许多关键问题做了独到的总结和开发。书中讨论了顺序程序的本质特征、程序描述和对程序行为(正确性)的推理,并通过一系列从简单到复杂的程序的思考和开发范例,阐释了基于严格的逻辑推理开发正确可靠程序的过程。 本书写于20世纪70年代中后期,但......一起来看看 《编程的修炼(中英双语)》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换