语言基础
- 第一个字符必须是一个字母、下划线(_)或美元符号($);剩下的其他字符可以是字母、下划线、美元符号或数字。
- 严格模式是一种不同的 JavaScript 解析和执行模型,ECMAScript 3 的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。要对整个脚本启用严格模式,在脚本开头加上这一行:
"use strict"; - ECMAScript 变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。有 3 个关键字可以声明变量:var、const 和 let。其中,var 在ECMAScript 的所有版本中都可以使用,而 const 和 let 只能在 ECMAScript 6 及更晚的版本中使用。
- var, const, let的区别
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
| 区别 | var | let | const |
|---|---|---|---|
| 是否有块级作用域 | 无 | 有 | 有 |
| 是否存在变量提升 | 有 | 无 | 无 |
| 是否添加全局属性 | 有 | 无 | 无 |
| 能否重复声明变量 | 有 | 无 | 无 |
| 是否存在暂时性死区 | 无 | 有 | 有 |
| 是否必须设置初始值 | 无 | 无 | 有 |
| 能否改变指针指向 | 有 | 有 | 无 |
- 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的几种情况:
- 已声明但未被初始化的变量默认赋值undefined
- 获取对象中不存在的属性时
- 函数需要实参,但是调用时没有传参,形参是undefined
- 当函数没有明确指定返回值时,默认返回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特点:
不同类型与布尔值之间的转换规则
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()
- 如果是Boolean值,true和false将分别转换为1和0。\
- 如果是数字值,只是简单的传入和返回。\
- 如果是null值,返回0。\
- 如果是undefined,返回NaN。\
- 如果是字符串,遵循下列规则:如果字符串截去开头和结尾的空白字符后,不是纯数字字符串,那么最终返回结果为NaN。如果是字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即“1”变成1,“123”会变成123,而“011”会变成11(前导的零被忽略了);如果字符串中包含有效的浮点格式,如“1.1”,则将其转换为对应的浮点数值(同样也会忽略前导零);如果字符串中包含有效的十六进制格式,例如”0xf”,则将其他转换为相同大小的十进制整数值;如果字符串是空的(不包含任何字符),则将其转换为0;如果字符串中包含除上述格式之外的字符,则将其他转换成NaN.
- 如果是对象,则调用对象的valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是NaN,则调用的对象的toString()方法,然后再次依照前面的规则转换返回的字符串值。
parseInt()
parseInt()函数可以将字符串转换成一个整数,与Number()函数相比,parseInt()函数不仅可以解析纯数字字符串,也可以解析以数字开头的部分数字字符串(非数字部分字符串在转换过程中会被去除)。
- 如果第一个字符不是数字字符或者负号,parseInt()就会返回NaN; 也就是说,用parseInt()转换空字符串会返回NaN。\
- 如果第一个字符是数字字符,parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符。\
- 如果字符串以”0x”开头且后跟数字字符,就会将其当作一个十六进制整数。\
- 如果字符串以”0”开头且后跟数字字符,就会将其当作一个八进制整数。\
- parseInt()函数增加了第二参数用于指定转换时使用的基数(即多少进制)。\
- 当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
基本引用类型
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
| Map | Object | |
|---|---|---|
| 附加的Key | Map没有默认的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 接口的对象(引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器)
可迭代对象支持哪些特性?
- for-of 循环
- 数组解构
- 扩展操作符
- Array.from()
- 创建集合
- 创建映射
- Promise.all()接收由期约组成的可迭代对象
- Promise.race()接收由期约组成的可迭代对象
- 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()方法
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属性指向构造函数
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 来自 成都去往上海
继承
代理与反射
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 对象。