JavaScript——基础篇

2019-06-17 admin

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

一、变量提升

何为变量提升?

在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实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里调用 规则:默认绑定、隐式绑定、显式绑定、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绑定的是那个上下文对象
  • 如果都不是的话,使用默认绑定

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

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

八、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、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,函数柯里化等,也有许多理解不到位的情况,日后会逐渐完善与补充。

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

[转载]原文链接:https://segmentfault.com/a/1190000019505052

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-67544.html

文章标题:JavaScript——基础篇

相关文章
javascript是什么意思
avaScript是Netscape开发的一个对象脚本语言,它使用在世界各地数以百万计的网页和服务器应用程序上。 网景的JavaScript是ecma - 262版的标准脚本语言,和公布的标准只有轻微的差异。 与广为流行的错误理解相反,Ja...
2015-11-12
21天学通javascript
简介: 本书是Javascript入门教程。Javascript是Web开发中应用最早、发展最成熟、用户最多的脚本语言。其语法简洁,代码可读性在众多脚本语言中最好,它在使用时不用考虑数据类型,是真正意义上的动态语言。本书总分为四篇,共21章...
2015-11-16
零基础-5小时开发一个electron应用-[实践]
一、背景 三、技能升级 ​ 明明可以用颜值取胜,非要靠才华?不对,明明可以用代码搞定,非要搞设计?步入正题,正好最近对electron比较感兴趣,又是要做工具,那就直接怼 1.electron介绍 ​ electron最开始不叫这个名字,叫...
2017-12-26
JavaScript的组成
一个完整的JavaScript由3个部分组成:核心(ECMAScript) 文档对象模型(DOM) 浏览器对象模型(BOM) ECMAScript 描述了该语言的语法和基本对象 ; DOM 描述了处理网页内容的方法和接口 ; BOM 描...
2015-11-12
javaScript+turn.js实现图书翻页效果实例代码
为了实现图书翻页的效果我们在网上可以看到很多教程 在这里推荐turn.js 网上的turn.js 有api 不过是英文的  很多人看起来不方便 .关于代码也是奇形怪状在这里我将详细讲解如何使用turn.js实现翻页效果 ,本篇文章只是讲解 ...
2017-03-16
JavaScript 事件流、事件处理程序及事件对象总结
JS与HTML之间的交互通过事件实现。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用监听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。这种在传统软件工程中被称为观察员模式,支持页面的行为与页面的外观之间的松散耦合。...
2017-04-05
JavaScript变量的声明
声明变量 变量在脚本中的首次亮相是在其声明中。 在变量首次出现时将会在内存中设置它,因此您稍后可在脚本中引用它。 应在使用变量之前先声明变量。 可以使用 var 关键字实现此目的。 &lt;span id=“mt9” class=“sent...
2015-11-12
7个提高效率的JavaScript调试工具
鐜板湪鐨凧avaScript浜嬪疄涓婂凡鐒舵垚涓轰簡娴佽�岀殑web璇�瑷€锛屽嵆浣垮畠骞朵笉瀹岀編銆傚緢澶氱▼搴忓憳涓嶅枩娆㈢敤JavaScript鍐欎唬鐮侊紝鏄�鍥犱负鍐欏埌鍚庢潵鎬讳細鍑虹幇鍚勭�嶈帿鍚嶅叾濡欑殑bug锛岃€屼笖鍦ㄥ紑...
2015-11-11
JavaScript短路原理精简代码
js中||和&amp;&amp;的特性帮我们精简了代码的同时,也带来了代码可读性的降低,虽然高效,但请灵活使用。 在js逻辑运算中,0、&quot;&quot;、null、false、undefined、NaN都会判为false,其他都为t...
2015-11-12
《JavaScript快速查询手册》PDF
下载地址:《JavaScript快速查询手册》PDF下载 http://pan.baidu.com/s/130rP8’ 简介: JavaScript快速查询手册 目录 前言 第一部分 命令查询 第二部分 JavaScript语句与运算符 第...
2015-11-16
回到顶部