JavaScript

214 阅读6分钟

语言基础

  • 第一个字符必须是一个字母、下划线(_)或美元符号($);剩下的其他字符可以是字母、下划线、美元符号或数字。
  • 严格模式是一种不同的 JavaScript 解析和执行模型,ECMAScript 3 的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。要对整个脚本启用严格模式,在脚本开头加上这一行:"use strict";
  • ECMAScript 变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。有 3 个关键字可以声明变量:var、const 和 let。其中,var 在ECMAScript 的所有版本中都可以使用,而 const 和 let 只能在 ECMAScript 6 及更晚的版本中使用。
  • var, const, let的区别 9899cec0800c45debf477f80ff4eac68.png

1、var声明在函数的外面是全局作用域,声明的变量是全局变量,可以在整个JS代码中生效;var在函数内部,是局部作用域,声明的变量是局部变量,仅在函数中生效。而let和const只在块级作用域生效。 2、在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。 3、var在全局作用域声明的变量有一种行为会挂载在window对象上,它会创建一个新的全局变量作为全局对象的属性,这种行为说不定会覆盖到window对象上的某个属性,而let, const声明的变量则不会有这一行为。

var value1 = "张三"
let value2 = "李四"
const value3 = "王五"
console.log(window.value1) // 张三
console.log(window.value2) // undefined
console.log(window.value3) // undefined
区别varletconst
是否有块级作用域
是否存在变量提升
是否添加全局属性
能否重复声明变量
是否存在暂时性死区
是否必须设置初始值
能否改变指针指向
  • var和let在循环中的区别
for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    })
};// 输出 5 5 5 5 5
/*这是因为setTimeout是异步执行,每一次for循环的时候,setTimeout都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里,等待执行。只有主线上的任务执行完,才会执行任务队列里的任务。也就是说它会等到for循环全部运行完毕后,才会执行function函数,但是当for循环结束后此时i的值已经变成了5。*/
for (var i = 0; i < 5; i++) {
    (function(i) {
        setTimeout(function timer() {
            console.log(i);
        });
    })(i);
}// 输出 0 1 2 3 4
/*通过闭包,将i的变量驻留在内存中,当输出i时,引用的是外部函数的变量值i,i的值是根据循环来的,执行setTimeout时已经确定了里面的的输出了。*/
for (let i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    })
};// 输出 0 1 2 3 4
/*for循环头部的let不仅将i绑定到for循环中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过var定义的变量是无法传入到这个函数执行域中的,通过使用let来声明块变量能作用于这个块,所以function就能使用i这个变量了;这个匿名函数的参数作用域和for参数的作用域不一样,是利用了这一点来完成的。这个匿名函数的作用域有点类似类的属性,是可以被内层方法使用的。*/
  • 有 6 种简单数据类型(也称为原始类型):Undefined、Null、Boolean、Number、String 和 Symbol。还有一种复杂数据类型叫 Object。

typeof特点:

对一个值使用 typeof 操作符会返回下列字符串之一: "undefined"表示值未定义; "boolean"表示值为布尔值; "string"表示值为字符串; "number"表示值为数值; "object"表示值为对象(而不是函数)或 null; "function"表示值为函数; "symbol"表示值为符号。

undefined特点:

出现undefined的几种情况:

  1. 已声明但未被初始化的变量默认赋值undefined
  2. 获取对象中不存在的属性时
  3. 函数需要实参,但是调用时没有传参,形参是undefined
  4. 当函数没有明确指定返回值时,默认返回undefined
  <script>
     console.log(Number(undefined));//NaN
     console.log(parseInt(undefined));//NaN
     console.log(parseFloat(undefined));//NaN
     console.log(+undefined);//NaN
     console.log(-undefined);//NaN
     console.log(undefined==null);//true
     console.log(!undefined);//true
     console.log(undefined==0);//false
  </script>

null的特点:

定义一个变量但初始化为null:const missingObject = null;

如何检测null:

const missingObject = null; 
const existingObject = { message: 'Hello!' }; 
missingObject === null; // => true 
existingObject === null; // => false

typeof null; // => 'object'

null vs undefined

null是javascript的关键字,可以认为是对象类型,它是一个空对象指针,和其它语言一样都是代表“空值”。不过 undefined 却是javascript才有的,为了区分空指针对象和未初始化的变量,它是一个预定义的全局变量。没有返回值的函数返回为undefined,没有实参的形参也是undefined。

typeof undefined             // undefined
typeof null                  // object
null === undefined           // false
null == undefined            // true

boolean特点:

不同类型与布尔值之间的转换规则

捕获.PNG

number特点:

最大值和最小值:Number.MAX_VALUE、 Number.MAX_VALUE

console.log(0/0); // NaN
console.log(-0/+0); // NaN
console.log(5/0); // Infinity
console.log(5/-0); // -Infinity

Number(),parseInt(),parseFloat()三者比较:
共同点:
Number():可以用于任何数据类型转换成数值;parseInt()、parseFloat(): 专门用于把字符串转换成数值;
不同点:
Number()

  1. 如果是Boolean值,true和false将分别转换为1和0。\
  2. 如果是数字值,只是简单的传入和返回。\
  3. 如果是null值,返回0。\
  4. 如果是undefined,返回NaN。\
  5. 如果是字符串,遵循下列规则:如果字符串截去开头和结尾的空白字符后,不是纯数字字符串,那么最终返回结果为NaN。如果是字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即“1”变成1,“123”会变成123,而“011”会变成11(前导的零被忽略了);如果字符串中包含有效的浮点格式,如“1.1”,则将其转换为对应的浮点数值(同样也会忽略前导零);如果字符串中包含有效的十六进制格式,例如”0xf”,则将其他转换为相同大小的十进制整数值;如果字符串是空的(不包含任何字符),则将其转换为0;如果字符串中包含除上述格式之外的字符,则将其他转换成NaN.
  6. 如果是对象,则调用对象的valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是NaN,则调用的对象的toString()方法,然后再次依照前面的规则转换返回的字符串值。

parseInt()
parseInt()函数可以将字符串转换成一个整数,与Number()函数相比,parseInt()函数不仅可以解析纯数字字符串,也可以解析以数字开头的部分数字字符串(非数字部分字符串在转换过程中会被去除)。

  1. 如果第一个字符不是数字字符或者负号,parseInt()就会返回NaN; 也就是说,用parseInt()转换空字符串会返回NaN。\
  2. 如果第一个字符是数字字符,parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符。\
  3. 如果字符串以”0x”开头且后跟数字字符,就会将其当作一个十六进制整数。\
  4. 如果字符串以”0”开头且后跟数字字符,就会将其当作一个八进制整数。\
  5. parseInt()函数增加了第二参数用于指定转换时使用的基数(即多少进制)。\
  6. 当parseInt()函数所解析的是浮点数字符串时,取整操作所使用的方法为“向下取整”。

string特点:
blog.csdn.net/weixin_4424…
charAt()     //获取指定位置处字符
concat()    //拼接字符串,等效于+,+更常用
slice(start, end)     //从start位置开始,截取到end位置,end取不到,不会修改字符串,而是返回一个子字符串。将所有负值参数都当成字符串长度加上负参数值
substring(start, end) //从start位置开始,截取到end位置,end取不到。将所有负参数值都转换为 0
substr(start, length)    //从start位置开始,截取length个字符。将第一个负参数值当成字符串长度加上该值,将第二个负参数值转换为 0\

let stringValue = "hello world";
console.log(stringValue.slice(-3)); // "rld"
console.log(stringValue.substring(-3)); // "hello world"
console.log(stringValue.substr(-3)); // "rld"
console.log(stringValue.slice(3, -4)); // "lo w"
console.log(stringValue.substring(3, -4)); // "hel"
console.log(stringValue.substr(3, -4)); // "" (empty string)

indexOf()    //返回指定内容在元字符串中的位置(如果没有找到子字符串, 则返回-1)
lastIndexOf() //从后往前找,只找第一个匹配的(如果没有找到子字符串, 则返回-1)
trim()   //只能去除字符串前后的空白
toUpperCase() //转换大写 重点
toLowerCase() //转换小写 重点
search()  //返回与正则表达式查找内容匹配的第一个字符串的位置。
replace()  //重点:替换字符串
split(s)   //将一个字符串分割为子字符串,然后将结果作为字符串数组返回
includes() //返回布尔值,表示是否找到了参数字符串。
startsWith() //返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith() //返回布尔值,表示参数字符串是否在原字符串的尾部。 repeat //方法返回一个新字符串,表示将原字符串重复n次
toString()和String()的区别:对于转换值的区别:toString()不能处理转换的值是null或undefined的情况,它遇到这种情况下会在console中报错:Uncaught TypeError: Cannot read property 'toString' of undefined。而String()是可以处理转换的值是null或undefined的参数,转换的值是null的返回值是null字符串,转换的值是undefined返回值是undefined字符串。

symbol特点:

object特点:
每个 Object 实例都有如下属性和方法:
constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是 Object()函数。
hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如 o.hasOwnProperty("name"))或符号。
isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。(第 8 章将详细介绍原型。)
propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用(本章稍后讨论的)for-in 语句枚举。与 hasOwnProperty()一样,属性名必须是字符串。
toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
toString():返回对象的字符串表示。
valueOf():返回对象对应的字符串、数值或布尔值表示。通常与 toString()的返回值相同。

  • for in和for of
for (var key in myObject) {
  if(myObject.hasOwnProperty(key)){
    console.log(key);
  }
}
// 拿到的是对象的属性名,拿到的是数组对象的下标,会遍历对象的整个原型链,更适合遍历对象。使用for-in可以遍历数组,但是会存在以下问题:1.index索引为字符串型数字(注意,非数字),不能直接进行几何运算。2.遍历顺序有可能不是按照实际数组的内部顺序(可能按照随机顺序)。3.使用for-in会遍历数组所有的可枚举属性,包括原型。原型方法method和name属性都会被遍历出来,通常需要配合hasOwnProperty()方法判断某个属性是否该对象的实例属性,来将原型对象从循环中剔除。
var myArray = [1, 2, 4, 5, 6, 7];
myArray.name = "数组";
myArray.getName = function() { return this.name; }
for (var value of myArray) {
    console.log(value);
}
// 与forEach()不同的是,它可以正确响应break、continue和return语句。适合用来遍历数组。因为for-of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name。
  • 函数
// ES6 支持函数带有默认参数
function myFunction(x, y = 10) { 
    // y is 10 if not passed or undefined 
    return x + y; 
} 
myFunction(0, 2) // 输出 2 
myFunction(5); // 输出 15, y 参数的默认值

// JavaScript 函数有个内置的对象 arguments 对象。
x = findMax(1, 123, 500, 115, 44, 88); 
function findMax() { 
    var i, max = arguments[0]; 
    if(arguments.length < 2) 
        return max; 
    for (i = 0; i < arguments.length; i++) { 
        if (arguments[i] > max) { 
        max = arguments[i]; 
        } 
    } 
    return max; 
}

变量、作用域与内存

  • js中参数传递是按值传递的
// 例子1
var count = 10;
function num(num1){
   num1 = 1;
   return num1;
}
var result = num(count1);
console.log(result);//1
console.log(count);//10,并未变成1

// 例子2
var person  = {
    name : "Tom"
};
function obj(peo){
    peo.name = "Jerry";
    return peo;
}
var result = obj(person);
console.log(result.name);// Jerry
console.log(person.name);// Jerry

//例子3
var person = {
    name : "Tom"
}; 
function obj(peo){
    peo = {
       name : "Jerry"
    };
    return peo;
}
var result = obj(person);
console.log(result.name);// Jerry
console.log(person.name);// Tom
  • typeof确定类型
let s = "Nicholas";
let b = true;
let i = 22;
let u;
let n = null;
let o = new Object();
console.log(typeof s); // string
console.log(typeof i); // number
console.log(typeof b); // boolean
console.log(typeof u); // undefined
console.log(typeof n); // object
console.log(typeof o); // object

基本引用类型

123.PNG

var patt = new RegExp('o');
patt.test("Hello World!");//true
patt.test("Hi!");//false

var patt = new RegExp('o');
var result1=patt.exec("Hello World!");//Array,result1[0]='o'
var result2=patt.exec("Hi!");//null

集合引用类型

Array

// 创建数组
let colors = new Array(20);
let colors = new Array("red", "blue", "green");
let colors = ["red", "blue", "green"]; // 创建一个包含 3 个元素的数组
let names = []; // 创建一个空数组
console.log(Array.from("Matt")); // ["M", "a", "t", "t"]
const arr1 = Array.of(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

// 检测是不是数组
if (value instanceof Array){
    // 操作数组
}

// 填充
const zeroes = [0, 0, 0, 0, 0];
zeroes.fill(5);// 用 5 填充整个数组
console.log(zeroes); // [5, 5, 5, 5, 5]
zeroes.fill(6, 3);// 用 6 填充索引大于等于 3 的元素
console.log(zeroes); // [0, 0, 0, 6, 6]
zeroes.fill(7, 1, 3);// 用 7 填充索引大于等于 1 且小于 3 的元素
console.log(zeroes); // [0, 7, 7, 0, 0];

// 栈方法
let colors = new Array(); // 创建一个数组
let count = colors.push("red", "green"); // 推入两项
alert(count); // 2
count = colors.push("black"); // 再推入一项
alert(count); // 3
let item = colors.pop(); // 取得最后一项
alert(item); // black
alert(colors.length); // 2

// 队列方法
let colors = new Array(); // 创建一个数组
let count = colors.push("red", "green"); // 推入两项
alert(count); // 2
count = colors.push("black"); // 再推入一项
alert(count); // 3
let item = colors.shift(); // 取得第一项
alert(item); // red
alert(colors.length); // 2

// 排序
let values = [1, 2, 3, 4, 5];
values.reverse();
alert(values); // 5,4,3,2,1
let values = [0, 1, 5, 10, 15];
values.sort();
alert(values); // 0,1,10,15,5

// concat()
let colors = ["red", "green", "blue"];
let colors2 = colors.concat("yellow", ["black", "brown"]);
console.log(colors2); // ["red", "green", "blue", "yellow", "black", "brown"]

// slice()
let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(1, 4);
alert(colors2); // green,blue,yellow,purple
alert(colors3); // green,blue,yellow

// splice()
let colors = ["red", "green", "blue"];
let removed = colors.splice(0,1); // 删除第一项
alert(colors); // green,blue
alert(removed); // red,只有一个元素的数组
removed = colors.splice(1, 0, "yellow", "orange"); // 在位置 1 插入两个元素
alert(colors); // green,yellow,orange,blue
alert(removed); // 空数组
removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
alert(colors); // green,red,purple,orange,blue
alert(removed); // yellow,只有一个元素的数组

// 搜索和位置,严格全等
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
alert(numbers.indexOf(4)); // 3
alert(numbers.lastIndexOf(4)); // 5
alert(numbers.includes(4)); // true
alert(numbers.indexOf(4, 4)); // 5
alert(numbers.lastIndexOf(4, 4)); // 3
alert(numbers.includes(4, 7)); // false
let person = { name: "Nicholas" };
let people = [{ name: "Nicholas" }];
let morePeople = [person];
alert(people.indexOf(person)); // -1
alert(morePeople.indexOf(person)); // 0
alert(people.includes(person)); // false
alert(morePeople.includes(person)); // true

// 迭代方法
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
alert(everyResult); // false
let someResult = numbers.some((item, index, array) => item > 2);
alert(someResult); // true

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let filterResult = numbers.filter((item, index, array) => item > 2);
alert(filterResult); // 3,4,5,4,3

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
alert(mapResult); // 2,4,6,8,10,8,6,4,2

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.forEach((item, index, array) => {
    // 执行某些操作
});

// reduce
// 数组求和
let arr = [1,2,3,4,5]
console.log(arr.reduce((a,b) => a + b)) // 15
console.log(arr.reduce((a,b) => a * b)) // 120

// 计算数组中每个元素出现的次数
var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice', 'Bob', 'Bob'];
var countedNames = names.reduce(function (allNames, name) {
  console.log(allNames,name);
  if (name in allNames) {
    allNames[name]++;
  }
  else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
console.log(countedNames);

// 去除数组中重复的元素
let myArray = ['a', 'b', 'a', 'b', 'c', 'e', 'e', 'c', 'd', 'd', 'd', 'd'];
let myOrderedArray = myArray.reduce(function (accumulator, currentValue) {
  if (accumulator.indexOf(currentValue) === -1) {
    accumulator.push(currentValue)
  }
  return accumulator;
}, []);
console.log(myOrderedArray);  // ['a','b','c','d']

map

const m = new Map();
alert(m.has("firstName")); // false
alert(m.get("firstName")); // undefined
alert(m.size); // 0

m.set("firstName", "Matt").set("lastName", "Frisbie");
alert(m.has("firstName")); // true
alert(m.get("firstName")); // Matt
alert(m.size); // 2

m.delete("firstName"); // 只删除这一个键/值对
alert(m.has("firstName")); // false
alert(m.has("lastName")); // true
alert(m.size); // 1

m.clear(); // 清除这个映射实例中的所有键/值对

alert(m.has("firstName")); // false
alert(m.has("lastName")); // false
alert(m.size); // 0
MapObject
附加的KeyMap没有默认的key值Object具有原型对象,所以它包含默认的key值,并且使用不当时会和自定义的key值产生冲突(在ES5中可以通过Object.create(null)来设置去掉默认的key值,但这种解决方法并不常用)
Key的种类Map的key值可以是任何类型的值,包括函数、Object和任意基础数据类型Object的key值只能是String或Symbol
Key的顺序Map中的key值排序简单直接,一个Map对象迭代键值对、Key、Value的顺序和插入时的顺序相同一般对象的键值是有顺序的,但这并不绝对,有时对象的键值排序会变得很复杂,所以最好不要依赖于插入的顺序。
大小Map的大小可以轻松通过size属性来获得Object的大小必须通过自行获取
迭代Map是可迭代对象,可以轻松完成迭代Object没有实现迭代协议,所以无法被for...of直接迭代(但可以自行实现迭代协议,或者使用Object.keys()Object.entries()来迭代对象的键值和实体,for...in也可以迭代Object的可枚举属性)
性能频繁增减键值对时表现会更好频繁增减键值对时表现较差
  • 内存占用:Object 和 Map 的工程级实现在不同浏览器间存在明显差异,但存储单个键/值对所占用的内存数量。都会随键的数量线性增加。批量添加或删除键/值对则取决于各浏览器对该类型内存分配的工程实现。不同浏览器的情况不同,但给定固定大小的内存,Map 大约可以比 Object 多存储 50%的键/值对。
  • 插入性能:向 Object 和 Map 中插入新键/值对的消耗大致相当,不过插入 Map 在所有浏览器中一般会稍微快一点儿。对这两个类型来说,插入速度并不会随着键/值对数量而线性增加。如果代码涉及大量插入操作,那么显然 Map 的性能更佳。
  • 查找速度:与插入不同,从大型 Object 和 Map 中查找键/值对的性能差异极小,但如果只包含少量键/值对,则 Object 有时候速度更快。在把 Object 当成数组使用的情况下(比如使用连续整数作为属性),浏览器引擎可以进行优化,在内存中使用更高效的布局。这对 Map 来说是不可能的。对这两个类型而言,查找速度不会随着键/值对数量增加而线性增加。如果代码涉及大量查找操作,那么某些情况下可能选择 Object 更好一些。
  • 删除性能:使用 delete 删除 Object 属性的性能一直以来饱受诟病,目前在很多浏览器中仍然如此。为此,出现了一些伪删除对象属性的操作,包括把属性值设置为 undefined 或 null。但很多时候,这都是一种讨厌的或不适宜的折中。而对大多数浏览器引擎来说,Map 的 delete()操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择 Map。

迭代器与生成器

原来的迭代方式有什么问题?

  • 迭代之前需要事先知道如何使用数据结构。数组中的每一项都只能先通过引用取得数组对象,然后再通过[]操作符取得特定索引位置上的项。这种情况并不适用于所有数据结构。
  • 遍历顺序并不是数据结构固有的。通过递增索引来访问数据是特定于数组类型的方式,并不适用于其他具有隐式顺序的数据结构。

可迭代对象 只有实现Iterable接口才算可迭代对象,实现接口必须支持迭代的自我识别能力(暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的 Symbol.iterator 作为键)和创建实现Iterator 接口的对象(引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器)

可迭代对象支持哪些特性?

  1. for-of 循环
  2. 数组解构
  3. 扩展操作符
  4. Array.from()
  5. 创建集合
  6. 创建映射
  7. Promise.all()接收由期约组成的可迭代对象
  8. Promise.race()接收由期约组成的可迭代对象
  9. yield*操作符,在生成器中使用

迭代器工作原理

创建一个指针对象,指向当前数据结构的起始位置;第一次调用next方法时,指针指向数据结构的第一个成员;接下来调用next方法,指针后移,直到指向最后一个成员。每次调用 next 方法都会返回一个值,这个值是一个 object,包含两个属性,value 和 done。value表示具体的返回值,done 是布尔类型的,表示集合是否遍历完成,完成则返回 true,否则返回 false。

let iteratorObj = {
    items: [1, 2, 'ljc'],
    // 部署Symbol.iterator属性
    [Symbol.iterator]: function () {
        let self = this
        let i = 0
        return {
            next: function () {
                // 类型转化为Boolean
                let done = (i >= self.items.length)
                // 数据确认
                let value = !done ? self.items[i++] : undefined
                // 数据返回
                return {
                    done,
                    value
                }
            }
        }
    }
}
for (let item of iteratorObj) {
    console.log(item); // 1 2 ljc
}

let arr = [1, 2, 3]
let it = arr[Symbol.iterator]()//返回迭代器对象
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
for (let item of arr) {
    console.log(item);
}

生成器

// 1.定义了一个生成器函数
function* foo() {
  console.log("00001")
  console.log("00002")
  yield console.log("aaaaa")
  console.log("00003")
  console.log("00004")
  yield
  console.log("00005")
  console.log("00006")
}

// 2.调用生成器函数, 会返回一个生成器对象
const generator =  foo()
// 当遇到yield时 会执行到与yield右边的代码后中断 yield左边如果有代码不执行 例如上面第一个yield后面还有代码也会执行
generator.next() // 00001 00002 aaaaa
generator.next() // 00003 00004
generator.next() // 00005 00006

生成器调用next方法返回值

function* foo() {
  console.log("00001")
  console.log("00002")
  yield
  console.log("00003")
  console.log("00004")
  yield
  console.log("00005")
  console.log("00006")
}
const generator =  foo()
console.log(generator.next()) // {value: undefined, done: false}
console.log(generator.next()) // {value: undefined, done: false}
console.log(generator.next()) // {value: undefined, done: true}

// 很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果
function* foo() {
  console.log("00001")
  console.log("00002")
  yield "aaaaa"
  console.log("00003")
  console.log("00004")
  yield "bbbbb"
  console.log("00005")
  console.log("00006")
}
const generator =  foo()
console.log(generator.next()) // {value: "aaaaa", done: false}
console.log(generator.next()) // {value: "bbbbb", done: false}
console.log(generator.next()) // {value: undefined, done: true}

给每个分段来传递参数

function* foo(name1) {
  console.log("00001", name1)
  console.log("00002", name1)
  const name2 = yield "aaaaa"
  console.log("00003", name2)
  console.log("00004", name2)
  const name3 = yield "bbbbb"
  console.log("00005", name3)
  console.log("00006", name3)
}
const generator =  foo("name1")
console.log(generator.next("name1"))
console.log(generator.next("name2"))
console.log(generator.next("name3"))

return传值后这个生成器函数就会结束,之后调用next不会继续生成值了

function* foo(name1) {
  console.log("00001", name1)
  console.log("00002", name1)
  const name2 = yield "aaaaa"
  console.log("00003", name2)
  console.log("00004", name2)
  const name3 = yield "bbbbb"
  console.log("00005", name3)
  console.log("00006", name3)
}

// 生成器返回一个生成器
const generator =  foo("name1")
console.log(generator.next("name1")) // {value: 'aaaaa', done: false}
// 通过return提前结束生成器
console.log(generator.return("name2")) // {value: 'name2', done: true}
// return之后再通过next调用不会继续生成值
console.log(generator.next("name3")) // {value: undefined, done: true}
console.log(generator.next()) // {value: undefined, done: true}
console.log(generator.next()) // {value: undefined, done: true}

throw()方法

blog.csdn.net/ccuucc/arti…

function* genratorFn1() {
  try {
    yield 1
    yield 2
  } catch (error) {}
}
const generator1 = genratorFn1()
generator1.throw() //会抛出错误,在生成器执行之前调用throw
 
const generator2 = genratorFn1()
generator2.next()
generator2.throw() //不会抛出错误,在内部进行了捕获
 
function* genratorFn2() {
  yield 1
  yield 2
}
const generate3 = genratorFn2()
generator2.next()
generator2.throw() //会抛出错误,未在内部进行了捕获

/**
throw()方法会向生成器内部注入错误,如果在生成器执行之前(即调用next()之前)调用throw,则会将错误抛向外部;若生成器内部没有捕获(try/catch),则会将错误抛出。throw的执行机制:在上次执行的位置的下一句注入错误,并抛弃此代码块,忽略块级作用域内部的后续任何代码,并在注入之后自动执行一次迭代,去找寻下一个正常可执行的yield语句。
*/
function* genratorFn() {
  try {
    yield 1
    yield 2
  } catch (error) {}
  try {
    yield 3
    yield 4
  } catch (error) {}
  try {
    yield 5
  } catch (error) {}
}
const generator = genratorFn()
 
console.log(generator.next()) //正常执行:{value: 1, done: false}
console.log(generator.throw())
//被生成器内部捕获,抛弃块作用域后序代码,并自动调用next()
//{value: 3, done: false}
console.log(generator.throw())//{value: 5, done: false}
console.log(generator.next())
//{value: undefined, done: true}

对象、类与面向对象编程

创建对象的几种方式

// 利用字面量创建对象
const obj = {
    uname: '张三疯',
    age: 18,
    sex: '男',
    sayHi: function () {
        console.log('hi~');
    }
}

// 利用new Object创建对象
var obj = new Object();
obj.uname = '张三疯';
obj.age = 18;
obj.sex = '男';
obj.sayHi = function () {
    console.log('hi~');
}

// 利用构造函数创建对象
function Star(uname, age, sex, song) {
    this.uname = uname;
    this.age = age;
    this.sex = sex;
    this.sing = function (song) {
         console.log(song);
    }
}
var ldh = new Star('刘德华', 18, '男');
console.log(ldh.uname);
console.log(ldh.age);
console.log(ldh.sex);
ldh.sing('冰雨');

// 工厂模式创建对象
function createObj(name,age) {
    let obj=new Object();
    obj.name=name;
    obj.age=age;
    obj.sayHi=function(){
        console.log(obj.name)
    }
    return obj;
}
let per=createObj('司藤',200)
console.log(per.age); //200
per.sayHi(); //司藤

对象深拷贝的方法

slice(), concat(), Object.assign(), es6的扩展运算符"...",Object.create()等都是浅拷贝

// 通过JSON.stringify() 和 JSON.parse() 将对象转为字符串之后在转为对象。
const obj = {name:'123'};
const obj2 = JSON.parse(JSON.stringify(obj))

// 递归拷贝,解决循环引用问题
/*
实现原理:使用递归的方式实现数组、对象的深拷贝。先判断各个字段类型,然后用递归解决嵌套数据。判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝。进行深拷贝的不能为空,并且是对象或者是数组。*/
function deepClone(obj){
  let objClone = Array.isArray(obj) ? [] : {};
  if (obj && typeof obj === 'object') {
    for(let key in obj){
      if (obj[key] && typeof obj[key] === 'object'){ // 判断对象的这条属性是否为对象
        objClone[key] = deepClone(obj[key]); // 若是对象进行嵌套调用
      }else{
        objClone[key] = obj[key]
      }
    }
  }
  return objClone; //返回深度克隆后的对象
}
let arrayA = [{a: 1, b: 2, aa: [{ab: 1, ac: [{ac: 1}]}]}];
let arrayB = deepClone(arrayA);
arrayB[0]['aa'][0].ab = 2;
console.log(arrayA); // [{a: 1, b: 2, aa: [{ab: 1, ac: [{ac: 1}]}]}]
console.log(arrayB); // [{a: 1, b: 2, aa: [{ab: 2, ac: [{ac: 1}]}]}]

判断对象是否相等

function isObjectValueEqual(a, b) {
     const aKeys = Object.keys(a);
     const bKeys = Object.keys(b);
     if (aKeys.length != bKeys.length) {
          return false;
     }
     for (let i = 0; i < aKeys.length; i++) {
         const key = aKeys[i]
         const aValue = a[key]
         const bValue = b[key]
         if(!b.hasOwnProperty(key)) return false
         if (aValue instanceof Object) {
             if (!this.isObjectValueEqual(aValue, bValue)) {
                 return false
             }
         } else {
             if (aValue !== bValue) return false
         }
     }
     return true
}

函数、原型对象、原型链

  • js中的函数和java中的不同,js中函数==函数对象==类==构造函数,概念类似于java中的类
  • js中的prototype理解为模板更容易理解一些
  • js中原型对象就是Person.prototype,Person.prototype就是原型对象,原型对象可以认为是创建其他实例时的模板。
  • js中所有东西都是对象,对象分为函数对象和其他对象。所有对象都有_proto_属性,指向该对象所对应的构造函数的原型对象,即person._proto = Person.prototype
  • js中的原型对象的constructor属性指向构造函数

v2-3a14864a10166a6d1558e249a0148c21_r.jpg

new一个对象的执行过程

function Person(name, age) {  
	this.name = name;  
	this.age = age;   
}  
var person = new Person("Alice", 23); 

// 1、创建一个空对象
var obj = new Object(); 

// 2、让Person中的this指向obj,并执行Person这个构造函数
var result = Person.call(obj); 

// 3、设置原型链,将obj的__proto__成员指向了Person函数对象的prototype成员对象
obj.__proto__ = Person.prototype; 

// 4、判断result的返回值类型,如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象。(因此调用call方法的时候可能返回了this,也可能没有返回)
if (typeof(result) == "object") 
    person = result;  
else
    person = obj;

call、apply、bind

const name = '小王', age = 17;
const obj = {
    name: '小张',
    objAge: this.age,
    myFun: function() {
    console.log(this.name + '年龄' + this.age);
    }
}
obj.objAge;  // 17
obj.myFun()  // 小张年龄 undefined
const name = '小王', age = 17;
const obj = {
    name: '小张',
    objAge: this.age,
    myFun: function(fm, t) {
        console.log(this.name + '年龄' + this.age, '来自' + fm + '去往' + t);
    }
}
const db = {
    name: '德玛',
    age: 99
}
obj.myFun.call(db,'成都','上海');     // 德玛 年龄 99  来自 成都去往上海
obj.myFun.apply(db,['成都','上海']);      // 德玛 年龄 99  来自 成都去往上海  
obj.myFun.bind(db,'成都','上海')();       // 德玛 年龄 99  来自 成都去往上海

继承

blog.csdn.net/wuliyouMaoz…

代理与反射

Reflect提供的13个静态方法

Reflect.apply(target, thisArg, args)
Reflect.construct(target, args)
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.defineProperty(target, name, desc)
Reflect.deleteProperty(target, name)
Reflect.has(target, name)
Reflect.ownKeys(target)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)
// Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined
const obj = {
  name: 'jack',
  age: 12,
  get userInfo() {
    return this.name + ' age is ' + this.age;
  }
}
Reflect.get(obj, 'name') // jack
Reflect.get(obj, 'age') // 12
Reflect.get(obj, 'userInfo') // jack age is 12

// 如果传递了receiver参数,在调用userInfo()函数时,this是指向receiver对象。
const receiverObj = {
  name: '小明',
  age: 22
};
Reflect.get(obj, 'userInfo', receiverObj) // 小明 age is 22
const obj = {
  name: 'jack',
  age: 12,
  set updateAge(value) {
    return this.age = value;
  },
}
Reflect.set(obj, 'age', 22);
obj.age // 22

// 如果传递了receiver参数,在调用updateAge()函数时,this是指向receiver对象。
const receiverObj = {
  age: 0
};
Reflect.set(obj, 'updateAge', 10, receiverObj) // 
obj.age         // 22
receiverObj.age // 10

代理在编程中很有用,它可以在目标对象之前增加一层“拦截”实现一些通用逻辑。

// Proxy中支持的拦截操作
handler.get(target, property, receiver)
handler.set(target, property, value, receiver)
handler.has(target, property)
handler.defineProperty(target, property, descriptor)
handler.deleteProperty(target, property)
handler.getOwnPropertyDescriptor(target, prop)
handler.getPrototypeOf(target)
handler.setPrototypeOf(target, prototype)
handler.isExtensible(target)
handler.ownKeys(target)
handler.preventExtensions(target)
handler.apply(target, thisArg, argumentsList)
handler.construct(target, argumentsList, newTarget)
// get()用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身,其中最后一个参数可选。
const user = {
  name: 'jack'
}
// 只有属性存在才返回值,否则抛出异常。
const proxy = new Proxy(user, {
  get: function(target, property) {
      if (!(property in target)) {
         throw new ReferenceError(`${property} does not exist.`);
      }
      return target[property];
    }
});
proxy.name // jack
proxy.age // ReferenceError: age does not exist.
// set()用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
let sizeValidator = {
    set: function(target, property, value, receiver) {
        if (typeof value == 'string' && value.length > 5) {
           throw new RangeError('Cannot exceed 5 character.');
        }
        target[property] = value;
        return true;
    }
};

const validator = new Proxy({}, sizeValidator);
let obj = Object.create(validator);
obj.name = '123456' // RangeError: Cannot exceed 5 character.
obj.age = 12 // 12

函数

为什么引入闭包?: JS 的函数内部可以使用函数外部的变量,而外部不可以使用内部的变量。
什么是闭包?: 如果一个函数访问了此函数的父级及父级以上的作用域变量,那么这个函数就是一个闭包。闭包的作用是将函数内部的变量 (局部变量)转化为全局变量。

const a = "global variable";
const F = function () {
    const b = "local variable";
    const N = function () {
        const c = "inner local";
        return b;
    };
    return N;
};
const d = F()
d()
// 用闭包定义能访问私有函数和私有变量的公有函数。
var counter = (function () {
    var privateCounter = 0; //私有变量
    function change(val) {
        privateCounter += val;
    }
    return {
        increment: function () {
            change(1);
        },
        decrement: function () {
            change(-1);
        },
        value: function () {
            return privateCounter;
        }
    };
})();

console.log(counter.value());//0
counter.increment();
console.log(counter.value());//1
counter.increment();
console.log(counter.value());//2

内存泄漏: 应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收。局部变量的生命周期在函数执行过后就结束了,此时便可将它引用的内存释放(即垃圾回收);全局变量的生命周期会持续到浏览器关闭页面。所以当我们过多的使用全局变量的时候也会导致内存泄漏的问题
js内存管理: JavaScript 是一种垃圾回收语言。垃圾回收语言通过周期性地检查先前分配的内存是否可达,帮助开发者管理内存。换言之,垃圾回收语言减轻了“内存仍可用”及“内存仍可达”的问题。两者的区别是微妙而重要的:仅有开发者了解哪些内存在将来仍会使用,而不可达内存通过算法确定和标记,适时被操作系统回收。
js中的垃圾回收过程: 1. 垃圾回收器创建了一个“roots”列表。Roots 通常是代码中全局变量的引用。JavaScript 中,“window” 对象是一个全局变量,被当作 root 。window 对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾);2. 所有的 roots 被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地检查。从 root 开始的所有对象如果是可达的,它就不被当作垃圾。3. 所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系统了。

// 闭包造成内存泄漏
function foo () {
    var name = 'foo'
    var age = 20

    function bar () {
        console.log(name)
        console.log(age)
    }
    return bar
} // 第二行至第八行为闭包函数 name 和 age 上升为自有变量
var fn = foo()
fn()
// fn函数调用完毕之后,foo函数会自动销毁,但foo函数中的变量name和age不会被销毁,因为在bar函数内部进行访问,并且根据垃圾回收机制,被另一个作用域引用的变量不会被回收。除非bar函数解除调用才能销毁。如果该函数使用的次数很少,不进行销毁的话就会变为闭包产生的内存泄漏。
// 解决办法
fn = null  // 最后加上这行,阻止内存泄漏

期约与异步函数

回调地狱

setTimeout(function () {
    console.log('我');
    setTimeout(function () {
        console.log('爱');
        setTimeout(function () {
            console.log('米');
            setTimeout(function () {
                console.log('饭');
            }, 1000);
        }, 1000);
    }, 1000);
}, 1000);
// Promise解决回调地狱
function getStr1() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('我');
        }, 1000);
    });
}
 
function getStr2() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('爱');
        }, 1000);
    });
}
 
function getStr3() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('米');
        }, 1000);
    });
}
 
function getStr4() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('饭');
        }, 1000);
    });
}
 
getStr1().then(function (data) {
    console.log(data);
    return getStr2();
}).then(function (data) {
    console.log(data);
    return getStr3();
}).then(function (data) {
    console.log(data);
    return getStr4();
}).then(function (data) {
    console.log(data);
})

Promise执行原理: promise有三种状态(执行中、完成、失败)。new Promise()是创建一个Promise实例,该实例接收一个匿名函数,函数有两个参数resolve和reject,这两个函数是Promise实现的,不需要手动实现。在匿名函数内部去进行判断,异步执行完之后的情况并分别调用resolve和reject去抛出一些数据,这些数据会在调用then()方法时被接收。其中resolve会将Promise的状态修改为成功。

function greet(){
    var promise = new Promise(function(resolve,reject){
        var greet = "hello  world";
        resolve(greet);
    });
    return promise;
}
greet().then(v=>{
    console.log(v); //hello  world
})

then(): then方法带有以下三个参数:成功回调,失败回调,前进回调,一般情况下只需要实现第一个,后面是可选的。Promise中最为重要的是状态,因为Promise执行then后还是Promise,所以就可以根据这一特性,不断的链式调用回调函数。then包含有两个方法,前一个执行resolve回调的参数,后一个执行reject回调的参数。

function greet(){
    var promise = new Promise(function(resolve,reject){
        var greet = "hello  world";
        resolve(greet);
    });
    return promise;
}
greet().then(v=>{
    console.log(v+1);
    return v;
})
.then(v=>{
    console.log(v+2);
    return v;
})
.then(v=>{
    console.log(v+3);
})

reject(): reject的作用就是把Promise的状态从pending置为rejected,这样在then中就能捕捉到reject的回调函数

function judgeNumber(num){
    var promise1 = new Promise(function(resolve,reject){
        num =5;
        if(num<5){
            resolve("num小于5,值为:"+num);
        }else{
            reject("num不小于5,值为:"+num);
        }
    });
    return promise1;
}

judgeNumber().then(
    function(message){
        console.log(message);
    },
    function(message){
        console.log(message);
    }
)

catch(): catch执行的是和reject一样的,也就是说如果Promise的状态变为reject时,会被catch捕捉到,不过需要特别注意的是如果前面设置了reject方法的回调函数,·则catch不会捕捉到状态变为reject的情况。catch还有一点不同的是,如果在resolve或者reject发生错误的时候,会被catch捕捉到。

function judgeNumber(num){
    var promise1 = new Promise(function(resolve,reject){
        num =5;
        if(num<5){
            resolve("num小于5,值为:"+num);
        }else{
            reject("num不小于5,值为:"+num);
        }
    });
    return promise1;
}

judgeNumber().then(
    function(message){
        console.log(message);
    }
)
.catch(function(message){
    console.log(message);
})

all(): Promise的all方法提供了并行执行异步操作的能力,在all中所有异步操作结束后才执行回调。

function p1(){
    var promise1 = new Promise(function(resolve,reject){
        console.log("p1的第一条输出语句");
        console.log("p1的第二条输出语句");
        resolve("p1完成");
    })
    return promise1;
}

function p2(){
    var promise2 = new Promise(function(resolve,reject){
        console.log("p2的第一条输出语句");
        setTimeout(()=>{console.log("p2的第二条输出语句");resolve("p2完成")},2000);

    })
    return promise2;
}

function p3(){
    var promise3 = new Promise(function(resolve,reject){
        console.log("p3的第一条输出语句");
        console.log("p3的第二条输出语句");
        resolve("p3完成")
    });
    return  promise3;
}

Promise.all([p1(),p2(),p3()]).then(function(data){
    console.log(data);
})

resolve(): 有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。Promise.resolve('foo') 等价于 new Promise(resolve => resolve('foo'))。resolve()本质作用是用来表示promise的状态为fullfilled,相当于只是定义了一个有状态的Promise,但是并没有调用它。
Promise.resolve方法的参数分成四种情况

  • 参数是一个 Promise 实例:Promise.resolve将不做任何修改、原封不动地返回这个实例。
  • 参数是一个thenable对象:thenable对象指的是具有then方法的对象。Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});
  • 参数不是具有then方法的对象,或根本就不是对象:如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
const p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s) // Hello
});
  • 不带有任何参数:Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。