文章目录
一、对象
1. 对象属性
let obj = {},
a = 0,
b = '0';
obj[a] = 'hello';
obj[b] = 'world';
console.log(obj[a]); //=>world
let obj = {},
a = Symbol(1),
b = Symbol(1);
obj[a] = 'hello';
obj[b] = 'world';
console.log(obj[a]); //=>hello
let obj = {},
a = {name: 'hello'},
b = {name: 'world'};
obj[a] = 'hello';
obj[b] = 'world';
console.log(obj[a]); //=>world
分析:
- 对象的属性可以是任何类型,但这不意味对象的键就是该类型,如果对象的键不是一个字符串时,那么最终浏览器会将其转换为 字符串 存储。
- Symbol定义的变量时 唯一 标识。
- 对象转为字符串的结果实际上都是 一致("[object Object]")
, 前提该对象没有valueOf。
2. 对象的浅克隆
① Object.assign
let obj1 = {
a: 1,
b: {
a: 22,
},
c: /^\d{11}$/,
d: new Date()
}
let obj3 = {};
let obj2 = Object.assign(obj3, obj1);
console.log(obj2 === obj3); // => true
分析:
- Object.assign()方法返回 第一个 参数,也即parameter1===return。
- Object.assign()方法只是一个浅克隆。
② 循环遍历
function copyLight(obj){
if(obj === null) return null;
let obj1 = {};
for(let key in obj){
// 判断是否为私有的属性
if(!obj.hasOwnProperty(key)) break;
obj1[key] = obj[key];
}
return obj1;
}
let obj2 = copyLight(obj1);
console.log(obj2);
3. 对象的深拷贝
① JSON.stringify()
let obj1 = {
a: 1,
b: {
a: {
a: 1,
},
},
c: /^\d{11}$/,
d: new Date(),
e: function(){console.log('a')}
}
// 方法一
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj2);
存在问题:
- 对于正则表达式,最终是一个 空对象。
- 拷贝的 时间不一致。
- 对于函数属性,会直接 丢失。
② 循环
function copyDeep(obj){
if(obj === null) return null;
if(typeof obj != 'object') return obj;
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
// 防止用户修改obj的constructor,导致最终的类型不一致
let obj1 = new obj.constructor;
for(let key in obj){
if(obj.hasOwnProperty(key)){
obj1[key] = copyDeep(obj[key]);
}
}
return obj1;
}
let obj2 = copyDeep(obj1);
console.log(obj2);
思路:
- 使用递归的操作,我们在函数执行最开始进行 所有的情况 的分析,最终可以直接减少for循环中的检测。
- 我们必须有 返回值,否则赋值无效。
二、作用域
1. 块级作用域
let a = [];
for(var i = 0; i < 10; i++) {
a[i] = function() {
console.log(i);
}
}
a[6](); // =>10
let a = [];
for(let i = 0; i < 10; i++) {
a[i] = function() {
console.log(i);
}
}
a[6](); // =>6
注意:
- 在for循环中使用var,不存在块级作用域,即此时var定义的变量为可被 全局 访问。
- 使用let存在 块级作用域。
三、闭包
1.
let a = 0,
b = 0;
function fun(a) {
fun = function(b) {
console.log(a + b++);
}
console.log(a++);
}
fun(1); // =>1
fun(2); // =>4
注意:
- ++运算符。
- 函数内部可以读写函数。
- 闭包有变量 保存 的作用。
四、预解析
1. 函数提升
var a = function() {
console.log('a1');
}
function a() {
console.log('a2');
}
a(); // => a1
分析代码执行:
/* 函数提升 */
var a; // a=>undefined
function a(){...a2} // a=>function(){...a2}
/* 函数执行 */
a = function(){...a1} // 1=>function(){...a1}
a() // =>a1
2. 变量和函数提升
var a = 1;
function a() {
console.log('a2');
}
console.log(a); //a=>1
- 代码执行步骤同上
3. 函数中的变量提升
var a = 1;
function fun() {
console.log(a);
var a = 12;
}
console.log(a); // => 1
fun(); // => undefined
var a = 1;
function fun() {
a = 6;
console.log(a);
var a = 12;
}
console.log(a); // => 1
fun(); // => 6
console.log(a); // => 1
注意:
- 函数中使用var申明的变量也会产生变量提升。
- 函数中的变量提升,不会被放在全局作用域中的最顶端,而实放在了 函数作用域 的最顶部。
五、函数
1. 函数执行的优先级
function Foo() {
getName = function() {
console.log(1);
}
return this;
}
Foo.getName = function() {
console.log(2);
}
Foo.prototype.getName = function() {
console.log(3);
}
var getName = function() {
console.log(4);
}
function getName() {
console.log(5);
}
Foo.getName(); // =>2
getName(); // =>4
Foo().getName(); // =>1
getName(); // =>1
new Foo.getName(); // =>2
new Foo().getName(); // =>3
new new Foo().getName(); // =>3
分析:
- 函数中的this指向他的 调用者。
- 函数内部可以对 函数本身 进行操作。
- Foo.fun() 和 Foo().fun() 的区别。
- new Foo.fun() 和 new Foo().fun() 的优先级问题。
- new new Foo().fun() 的优先级。
2. 普通函数和箭头函数
function A() {
alert(1);
}
function Func() {
A = function() {
alert(2);
}
return this;
}
Func.A = A;
Func.prototype = {
A: ()=>{
alert(3);
}
}
A(); // => "1"
Func.A(); // => "1"
Func().A(); // => "2"
new Func.A(); // => "1"
new Func().A(); // => "3"
new new Func().A(); // => 报错
注意:
- 函数在赋值时 不存在 地址赋值。
- 箭头函数中不会绑定 this,因此箭头函数不能被 new。
六、事件循环机制
`
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function(){
console.log('setTimeout1');
}, 100);
setTimeout(function(){
console.log('setTimeout2');
}, 0);
async1();
new Promise((res, rej)=>{
console.log('promise1');
res();
}).then(data=>{
console.log('promise2');
});
console.log('script end');
/*
=> script start -> async1 start -> async2
-> promise1 -> script end -> async1 end ->
promise2 -> setTimeout2 -> setTimeout1
*/
知识点:
- 主任务,微任务、宏任务的执行顺序为:主 > 微 > 宏。
- async 创建的函数执行时,使用 await 之后会产生什么特点。
- promise创建的任务为微任务,但是注意 构造函数 中传入的 执行函数却是主任务,因为 new 是一个主任务。
七、趣味JavaScript
1. 你不知道的==运算符
[] == [] // => false
[] == ![] // => true
[] == ![][] // =>fase
分析:
- 对象,数组,函数在==中是判断地址。
- 当两边数据类型不一致,同时含有对象、数组时会进行转换: -> toString() -> Number()
- 数组的toString为所有的集合
{} == {} // => false
{} == !{} // => false
{} == !!{} // => false
注意:
- 六种情况转换为boolean为fales,其他都为true。分别是: “”, 0, undefined, null, NaN, false。
NaN == NaN // => false
undefined == null // => true
注意:
- NaN和自己比较都是false,那么NaN和其他比较都是false
- undefined == null为true。
2. a在什么情况下,下面等式成立?
a == 1 && a == 2 && a == 3
① a为对象
let a = {i:0};
a.toString = function() {
return ++this.i;
}
a.valueOf = function() {
return ++this.i;
}
if(a == 1 && a == 2 && a == 3){
console.log('条件成立');
}
注意:
- 对象和其他类型判断时,会触发自己的 valueOf()和String() 方法。
- 原始valueOf()和toString() 之间的区别:
- valueOf函数返回的依然是数据的本身,而toString()返回的是一个字符串。
- valueOf先于toString之前执行。
② a为window的属性
var i = 0;
Object.defineProperty(window, 'a', {
get: function() {
return ++i;
}
})
if(a == 1 && a == 2 && a == 3){
console.log('条件成立');
}
思路:
- 使用了Object.defineProperty() 进行了数据的劫持
- 需要注意该api不允许对经定义的属性进行访问,因此借用了第三变量 i 做了一个中间的桥梁。
③ a为数组
let a = [1, 2, 3];
a.toString = shift;
if(a == 1 && a == 2 && a == 3){
console.log('条件成立');
}
分析:
- 原理和第一种相似,在这里不再重复说明。