文章目录
实用开发小技能
1. 解构赋值
// 数组的解构 使用 [ ]
let [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// 对象的解构 使用 { }
const Stack = {
name: 'Tony Stack',
sex: 'male',
sayhello: function(){console.log('hello');}
}
let {name, sex, sayhello} = Stack // 变量名必须是 Stack对象的属性名或方法名
2. 对象的简化写法
const name = 'xiaoming'
function doHomework(){console.log('do home work');}
const student = {
name,
doHomework,
sleep(){console.log('sleepping!');}
}
3. 扩展运算符 ...
function myprint(){
console.log(arguments);
console.log(...arguments);
}
myprint(1,2,3)
// 输出结果
[Arguments] { '0': 1, '1': 2, '2': 3 } // 一个数据对象
1 2 3 // 三个数据对象
// 数组的合并
const arr_1 = [1,2,3]
const arr_2 = ['a','b','c']
const arr_3 = [...arr_1, ...arr_2]
const arr_4 = arr_1.concat(arr_2)
console.log(arr_3); // [ 1, 2, 3, 'a', 'b', 'c' ]
console.log(arr_4); // [ 1, 2, 3, 'a', 'b', 'c' ]
4. 精准判断数据类型的的方法: Object.prototype.toString.call(obj)
var let const 之间的区别
ES6 提出了块级作用域的概念, 用于约束变量的作用范围. 一个 { } 就是一个块级作用域.
- var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
- let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
- const用来声明常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
{
var a = 1
let b = 2
const c = 3
}
console.log(a) // 1
console.log(b) // b is not defined
console.log(c) // c is not defined
const 声明的常量不可变,但其内部属性可变:
const定义的引用类型只要指针不发生改变,其他的不论如何改变都是允许的。
const constant = 'Good morning!'
constant = 'Good Evening!' // 报错,无法赋值给常量
const person = {
name: '张三',
age: 18
}
person.name = '李四' // 可以修改
person.sex = '男' // 可以添加新的属性
console.log(person) // {name: "李四", age: 18, sex: "男"}
person = { // 报错,无法赋值给常量, 这是因为指针发生了改变
name: '王五',
age: 30
}
函数
- 关键字 arguments : 用于获取参数对象:
- 它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。
- arguments最常用于判断传入参数的个数。
- arguments 不是 Array 类型。
function printArgs(){
console.log(arguments);
console.log(Object.prototype.toString.call(arguments));
}
printArgs(1,2,3,4,5)
<!-- 输出结果 -->
[Arguments] { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5 }
[object Arguments]
- 剩余参数 rest:
- rest参数只能写在最后,前面用
...标识 - 传入的参数先绑定a、b,多余的参数以数组形式交给变量rest
- rest参数只能写在最后,前面用
function printArgs(a, b, ...rest){
console.log(a);
console.log(b);
console.log(rest);
console.log(Object.prototype.toString.call(rest));
}
printArgs(1,2,3,4,5)
<!-- 输出结果 -->
1
2
[ 3, 4, 5 ]
[object Array]
- 使用匿名变量的函数会立即执行, 不需调用:
(function (x) {
return x * x;
})(3);
高阶函数 map、reduce、filter、sort
一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
map —— 用于遍历数组时实现特定功能
- map() 方法定义在JavaScript的 Array 中,需要传入一个回调函数:
function pow(x){
return x * x;
}
let arr = [1,2,3,4,5];
results = arr.map(pow);
console.log(results); // [1, 4, 9, 16, 25]
reduce 的原理
reduce 函数的语法 : arr.reduce(callback, [initialValue])
- initialValue 是可选参数, 作为初始值, 可以不传.
- reduce 需要传入一个特别的回调函数 :
callback(prev, cur [,index , array]); - 该回调函数会作用到数组中的每一个元素;
- 当reduce没有传入初始值时, 数组的第一个元素
arr[0]作为callback的prev, 数组的第二个元素arr[1]作为callback的cur; 执行callback[arr[0], arr[1]]会得到一个返回结果result, 然后将result作为新的prev, 将数组第下一个元素arr[2]作为cur, 接着执行callback(result, arr[2]), 依次类推, 直到遍历整个数组. - 当reduce传入了初始值
initialValue时,initialValue会作为第一个prev, 而数组的第一个元素作为cur, 执行callback(initialValue, arr[0])
function add(prev, cur){
return prev + cur;
}
let arr = [1,2,3,4,5];
console.log(arr.reduce(add)); // 15
console.log(arr.reduce(add, 100)); //115
filter —— 过滤
- 和 map( ) 类似,Array 的 filter( ) 也接收一个函数。
- filter( ) 把传入的函数依次作用于每个元素,然后根据返回值是
true还是false决定保留还是丢弃该元素。 - 返回一个过滤后的 Arry。
let list = [1,2,3,4,5];
let newList = list.filter(n => n > 3); // 简写的箭头函数 (n)=>{n>3}
console.log(newList); // [4,5]
console.log(list); // [1,2,3,4,5]
三个高阶函数综合使用:
let list = [1,2,3,4,5,6,7,8,9,10];
let total = list.filter(function(n){ // filter 筛选偶数
return n % 2 === 0
}).map(function(n){ // map 值乘以2
return n * 2
}).reduce(function(priviousValue, n){ // reduce 累加
return priviousValue + n
})
// 用箭头函数简写上面代码
let total = list.filter(n => n % 2 === 0).map(n => n * 2).reduce((priv, n) => priv + n)
console.log(total); // total等于60
sort —— 排序
- sort() 方法默认把所有元素先转换为 String 再排序。
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]
- sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。
- 通常规定,对于两个元素x和y,如果认为x < y,则返回-1,如果认为x == y,则返回0,如果认为x > y,则返回1,这样,排序算法就不用关心具体的比较过程,而是根据比较结果直接排序。
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {return -1}
if (x > y) {return 1}
return 0;
});
console.log(arr); // [1, 2, 10, 20]
作用域链和执行上下文
什么是作用域,什么是作用域链?
- 规定变量和函数的可使用范围称作作用域。
- 每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。
Js中的上下文。
在JS中,称之为“执行上下文,Execution Context、EC”。
JS是单线程的,运行在全局EC,每进入一个function,就做一次入栈操作,向栈顶压入一个属于该function的新的EC。若function中又调用了另一个function,则再执行一次入栈…依次执行完再依次出栈,回到全局EC。全局EC一定是在栈底,在浏览器关闭后出栈。
global EC -> funA EC -> funB EC -> funC EC
EC 的结构:
EC = {
VO: {}, // 变量对象
scopeChain: {}, // 作用域链
this: {} //在EC被创建时,会确定this的指向。
}
假设 “global EC”中存在一个变量“arg”,那么“funC EC”能够通过它的作用域链访问到变量“arg”。
闭包
闭包是指有权访问另外一个函数作用域中的局部变量的函数。声明在一个函数中的函数,叫做闭包函数。而且内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
闭包会创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。
function outerFn(){
let i = 0;
function innerFn(){
console.log(++i);
}
return innerFn; // 闭包的返回值是一个函数
}
/* 每次执行外部函数 outerFn(),都会开辟一个新的内存地址,
这个地址存储了返回结果 innerFn 函数 */
outerFn()(); // 1
outerFn()(); // 1
outerFn()(); // 1
// 把 outerFn() 开辟的内存地址赋给一个变量
const fn1 = outerFn();
fn1(); // 1
fn1(); // 2
fn1(); // 3
/* 三次执行 fn1(),实际上是在同一个内存空间里面执行 innerFn)() 函数
* 在闭包环境中,外部函数 outerFn() 执行完成之后会销毁,
* 但局部变量 i 被内部函数 innerFn 引用了,
* 所以 i 不会被销毁,而是保存在了 fn1 的内存中 */
const fn2 = outerFn();
fn2(); // 1
fn2(); // 2
fn2(); // 3
- 闭包函数访问其父函数的变量时,该变量的值在父函数的作用域内是被共享的,这是闭包函数的一个诟病。
function parent() {
let x = 1;
function child1() {
console.log(++x);
};
function child2() {
console.log(--x);
};
return [child1, child2];
}
const [fn1, fn2] = parent();
fn1(); // 2
fn1(); // 3
fn2(); // 2
fn2(); // 1
// 解决办法,添加一个中间变量 temp
function child1() {
let temp = x;
console.log(++temp);
};
function child2() {
let temp = x;
console.log(--temp);
};
- 闭包函数不要轻易引用循环变量,或者后续会发生变化的变量。
- 闭包函数引用循环变量时,利用块级变量 let 可以解决变量共享问题。
箭头函数及其this指向
Arrow Function 有两种形式:
- 函数体只包含一个 表达式:
x => x * x
// 等价于
function (x) {return x * x}
- 函数体包含 多条语句:
(x, y, ...rest) => {
let i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数中的this:
- 箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window; 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this。
- 箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。
const obj = {
fn1: function() {
console.log(this);
},
fn2: () => {
console.log(this);
},
}
obj.fn1(); // obj {fn1: ƒ, fn2: ƒ}
obj.fn2(); // window {...}
- 由于t箭头函数中的this已经在其定义时被静态绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略.
原型与原型链
JS 所创建的每一个函数,解析器都会向函数中添加一个属性prototype(原型),
prototype是一个对象,指向了当前构造函数的引用地址。
使用构造函数创造的每一个对象中都会有一个属性__proto__(隐式原型),这个属性指向构造函数的prototype。
// 定义一个构造函数
function Person(name,age){
this.name = name;
this.age = age;
}
// 在原型中存在的属性和方法,可以被构造函数创建出来的对象调用
Person.prototype = {
sayhello(){
console.log("Hello, I'm " + this.name);
}
}
// 通过构造函数创建新的对象
const Iron_Man = new Person('Tony Stack',33);
console.log(Iron_Man.__proto__ == Person.prototype); // true
Iron_Man.sayhello(); // Hello, I'm Tony Stack
如果把原型里面的 sayhello 属性定义在 Person 对象中,那么由 Person 创建的每一个对象都显性的包含了 sayhello 属性,这个属性占据了内存空间。
__proto__和prototype的详细描述:
__proto__属性是对象所独有的;prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__属性;__proto__属性的作用:当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__所指向的原型里找;如果原型里也没有这个属性,解释器会去原型的原型里继续查找,直到__proto__的终点null,再往上找就相当于在null上取值,会报错。通__proto__属性将对象连接起来的这条链路即我们所谓的原型链。prototype属性的作用:让该函数所实例化的对象都可以找到公用的属性和方法,还能实例对象占用内存占用。
迭代器、生成器
迭代器 Iterator:是一种接口(在JS中就是对象里面的一个属性Symbol.iterator,为各种不同的数据结构提供统一的访问方式。任何数据结构只要部署了Iterator接口,就可以完成遍历操作。
- es6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要提供给 for of 使用。
- 原生具备 iterator 接口的数据:Array、Arguments、Set、Map、String、TypedArray、NodeList
const arr = [1, 2, 3]
let iterator = arr[Symbol.iterator]()
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
Symbol.iterator 属性里存放了一个函数,这个函数实现了 next 方法。
迭代器的工作原理:
- 创建一个指针对象,指向当前数据结构的起始位置;
- 第一次调用next方法,指针自动指向数据结构的第一个成员;
- 接下来不断调用next方法,指针一直往后移,直到指向最后一成员;
- 每次调用next方法都会返回一个包含value和done属性的对象。
实现迭代器自定义遍历数据:
const obj = {
data: ['a', 'b', 'c'],
// 声明 Symbol.iterator 属性,并实现next方法
[Symbol.iterator](){
let index = 0;
return {
next: () => {
if (index < this.data.length) {
const result = {value:this.data[index], done:false};
index++;
return result;
} else {
return {value:undefined, done:true}
}
}
}
}
}
for (let v of obj) {
console.log(v);
}
生成器:其实就是一个带*号的特殊函数,可以用来做异步编程,避免以往的“回调地狱”式异步编程。
function * gen(){
console.log('hello');
}
let iterator = gen();
console.log(iterator); // 生成器返回的结果是一个 Generator 对象
iterator.next() // Generator 对象里封装了next() 方法,可以迭代
<!--输出结果-->
Object [Generator] {}
hello
生成器的特点:
- 生成器函数返回的是可迭代对象。
- 生成器函数里面可以使用“关键字 yield”控制代码的执行。
function * gen(){
yield 'step one';
yield 'step two';
yield 'step three';
}
let iterator = gen();
iterator.next(); // { value: 'step one', done: false }
iterator.next(); // { value: 'step one', done: false }
iterator.next(); // { value: 'step one', done: false }
iterator.next(); // { value: undefined, done: true }
生成器函数可以接收参数:
- 第 n+1 次调用next方法时传入的参数会作为第 n 次调用next方法的返回结果。
function * gen(arg){
console.log(arg); // 1
const one = yield 'step one';
console.log(one); // 2
const two = yield 'step two';
console.log(two); // 3
const three = yield 'step three';
console.log(three); // undefined
}
let iterator = gen(1);
iterator.next();
iterator.next(2); // 2 传给了第一次 next 的结果
iterator.next(3); // 3 传给了第二次 next 的结果
iterator.next();
Promise 异步编程
Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。。
Promise对象有以下两个特点。
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- Promise对象的状态改变,只有两种可能:从 pending 变为 fulfilled 或者从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。
使用 Promise:
- 创建一个Promise对象:
new Promise( function( resolve,reject ) {} ),Promise需要传入一个函数作为参数,该函数又需要传入两个参数(resolve、reject这两个参数自身也是函数)。- resolve 函数用于处理Promise对象从pending变为fulfilled。
- reject 函数用于处理Promise对象从pending变为rejected。
- 使用Promise的时候一般是包在一个函数中(在函数内部return Promise对象),在需要的时候去运行这个函数。
- Promise对象的 then() 方法:异步操作请求成功时,调用then方法接收resolve传递的参数,执行异步操作。它就相当于回调函数callback,只不过是把callback从
fun(arg, callback){}函数内部放到了外部fun(arg,){}.callback。 - Promise对象的 catch() 方法:异步操作请求失败时,代码执行到此处产生异常,但程序不会终止,而是由catch捕获异常并进行处理。
- Promise的 all() 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。
let isTrue = false
function runAsync(){
let p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('pending')
if (isTrue){
resolve('fulfilled')
}else {
reject('rejected')
}
}, 3000);
})
return p
}
runAsync().then(function(data){
console.log(data) // 成功时,打印 resolve 传过来的参数
}).catch(function(error){
console.log(error) // 失败时,打印 reject 传过来的参数
})
// 链式异步操作
runAsync().then( (data) => {
console.log(data) // 第一个异步请求的回调处理
new Promise( (resolve, reject) => {
resolve('第二个异步请求操作')
}).then( (data) => {
console.log(data) // 第二个异步请求的回调处理
})
})
本文深入探讨JavaScript的核心概念,包括var、let、const的区别,高阶函数如map、reduce、filter和sort的使用,原型链,迭代器、生成器以及Promise异步编程。讲解了作用域链、执行上下文、闭包和箭头函数的this指向,展示了JavaScript的强大和灵活性。
9956

被折叠的 条评论
为什么被折叠?



