JS
数据类型
基本类型(存栈):Bboolean、Number、String、Undefined、Null、Symbol、BigInt
引用类型(存堆):Object
类型判断
- typeof,用于除 null 外的基本类型
注意:
typeof NaN === "number";
-
instanceOf,基于原型链查找
-
Array.isArray(),用于判断数组
-
终极判断 Object.prototype.toString.call()
"[object Array]";
- 判断是否为空对象
var obj = {};
Object.prototype.toString.call(obj) &&
Object.getOwnPropertyNames(obj).length === 0;
类型转换
JS中只有三种类型转换:
- 转数字
- 转布尔
- 转字符串
进行==时
- 首先判断两者类型是否相同,如果相等,判断值是否相等
- 如果类型不同,进行类型转换
- 判断比较的是否是 null 或者是 undefined, 如果是, 返回 true .
- 判断两者类型是否为 string 和 number, 如果是, 将字符串转换成 number
- 判断其中一方是否为 boolean, 如果是, 将 boolean 转为 number 再进行判断
- 判断其中一方是否为 object 且另一方为 string、number 或者 symbol , 如果是, 将 object 转为原始类型再进行判断
对象转原始类型流程:
- toPrimitive()方法
- 调用valueOf(),如果转换为原始类型,则返回
- 调用toString(),如果转换为原始类型,则返回
- 如果都没有返回原始类型,会报错
// 说说'1'.toString()过程
var s = new String('1');
s.toString();
s = null;
// [] == ![]
== 两边转数字再进行比较
// if(a == 1 && a == 2)成立
var a = {
value: 0,
valueOf: function() {
this.value++;
return this.value;
}
};
console.log(a == 1 && a == 2)
模拟实现instanceof
原理:基于原型链查找
function instanceof2(left, right) {
// 基本类型
if(typeof left !== 'object' || left === null) {
return false;
}
// 获取left的原型
let _proto = Object.getPrototypOf(left);
// 原型链查找
while(true) {
if(_proto === null ) return false;
if(_proto === right.prototype) return true;
_proto = Object.getPrototypOf(_proto);
}
}
// 测试实例
let obj = new Object();
instanceof2(obj, Object);
概念理解
变量提升和暂时性死区
变量提升就是变量在声明之前就可以使用,值为undefined。
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取
原型和原型链
每个函数都有一个prototype属性,这个属性是一个对象,这个对象指向了能被所有该构造函数实例共享的属性和方法。这个对象我们称为实例的原型。
每一个JavaScript对象(除了null)都具有的一个属性,叫__proto__,这个属性会指向该对象的原型
// 获取对象的原型
1. obj.constructor.prototype
2. obj.__proto__
3. Object.getPrototypeOf()
当我们访问一个对象的属性时候,会先在这个对象本身上找,如果没有没有会通过__proto__查找这个对象的原型,如果原型没有会再通过__proto__查找原型的原型,直到Object.prototype,这个过程就叫原型链
作用域
规定了如何查找变量,也就是当前执行代码对变量的访问权限。JavaScript采用静态作用域,也就是函数的作用域在函数定义的时候就决定了。
动态作用域则是在函数执行时才决定。
var name = 'a';
function foo() {
console.log(name);
}
function bar() {
var name = 'b';
foo();
}
bar(); // 'a'
执行上下文
执行一段可执行代码(函数、全局、eval)时,所做的准备工作。包括:
- 变量对象
- 作用域链
- this
变量对象
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
- 全局上下文的变量对象初始化是全局对象
- 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始值
- 在代码执行阶段,会再次修改变量对象的属性值
先处理函数声明,再处理变量声明
console.log(foo); // function foo
function foo(){
console.log("foo");
}
var foo = 1;
作用域链
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有就会从父级中查找。这样由多个执行上下文的变量对象构成的链表就构成了作用域链
当函数创建时,会有一个名为 [[scope]] 的内部属性保存所有父变量对象
this
只在执行时确定,指向调用的对象
- 在函数中
- 作为对象方法
- 作为构造函数
- 使用call bind apply
- 箭头函数中的this,继承它上一层中的this
闭包
能够访问既不是函数参数也不是函数局部变量的函数
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]();
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = (function (i) {
return function(){
console.log(i);
}
})(i);
}
data[0]();
data[1]();
data[2]();
call/apply
func.call(thisArg, arg1, arg2, ...)
func.apply(thisArg, [argsArray])
call和apply使用一个指定的this和若干个指定的参数值(apply的参数则是一个数组或类数组)的前提下执行某个函数
Function.prototype.call2 = function (context, ...args) {
// 判断浏览器还是Node环境
if(!context) {
context = typeof window === 'undefined' ? global : window;
}
// 将函数作为当前上下文context的方法并执行
context.fn = this;
var result = context.fn(...args);
delete context.fn;
return result;
}
Function.prototype.apply2 = function (context, args) {
// 判断浏览器还是Node环境
if(!context) {
context = typeof window === 'undefined' ? global : window;
}
context.fn = this;
let result = typeof args? context.fn() : context.fn(...args);
delete context.fn;
return result;
}
bind
bind返回一个函数,bind的第一个参数作为this,可以传入参数
返回的新函数不是一个普通的function,而是一个bound function,简称绑定函数
当执行fn1时,本质上等于window.fn1(),如果this还能被改变,那this岂不是得指向window,那bind方法就没太大意义了
Function.prototype.bind2 = function(context) {
if(typeof this !== 'function'){
throw new TypeError('need a function');
}
let self = this;
// bind传递的函数参数
let _args = [...arguments].slice(1);
function Fn() {};
let bound = function() {
let args = [..._args, ...arguments];
context = this instanceof Fn ? this : context;
// 绑定this和参数
return self.apply(context, args);
}
// 绑定原型链
Fn.prototype = this.prototype;
bound.prototype = new Fn();
return bound;
}
// 测试实例
var name = 'Jack';
function person(age){
console.log(this.name , age);
}
person();
let result = person.bind({name: 'cc'}, 23);
result();
new
- 新生成了一个对象
- 对象连接到构造函数原型上
- 执行构造函数代码,并绑定this到这个对象
- 返回新对象
注:当在构造函数中返回一个对象时,内部创建出来的新对象就被我们返回的对象所覆盖
function new2() {
// 新生成了一个对象
let obj = {};
let [constructor, ...args] = [...arguments];
if (typeof constructor !== 'function') {
throw 'this param must be a function'
}
// 对象连接到构造函数原型上
obj.__proto__ = constructor.prototype;
// 执行构造函数代码,并绑定this到这个对象
let res = constructor.apply(obj, args);
// 判断构造函数返回的是否为对象
if ((typeof res === 'object' && res !== null) || typeof res === 'function') {
return res;
}
return obj;
}
// 测试实例
function Persion(name, age) {
this.name = name;
this.age = age || 23;
}
Persion.prototype.sex = true;
let p = new2(Persion, 'hh');
继承
原型链继承
function Parent () {
this.name = 'kevin';
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1.getName()) // kevin
引用类型的属性被所有实例共享
创建子类的实例时,不能向父类传参
构造函数继承
function Parent (name) {
this.name = name;
}
function Child (name) {
Parent.call(this, name);
}
var child1 = new Child('kevin');
console.log(child1.name); // kevin
var child2 = new Child('daisy');
console.log(child2.name); // daisy
方法都在构造函数中,无法复用
子类无法访问父类原型中的方法
组合继承
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
// 子类构造函数中调用父类构造函数
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('kevin', '18');
父类的构造方法会调用2次
寄生组合式继承
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
// 子类构造函数中调用父类构造函数
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var child2 = new Child('kevin', '18');
注:ES6中extends使用babel编译到ES5,使用的也是寄生组合式继承
EventLoop
相关文章尝试弄懂JavaScript的运行机制
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
XHR和fetch
xhr和fetch都是JavaScript原始提供的异步请求接口,fetch实现了Promise API。
Ajax是jQuery对xhr的封装,axios也是对xhr的封装,但它实现了Promise API。
高阶函数
一个函数他的参数或者返回值是另一个函数的函数
参考
ES6
let和const
默认使用const,只有在确实需要修改变量的时候,才使用let
let和var区别:
1、let块级作用域,var只有函数和全局作用域
2、let不存在变量提升
3、相同作用域不允许重复声明,var的话可以但是会覆盖
变量的解构赋值
从数组和对象中提取值,对变量进行赋值。如果解构不成功,变量的值就会被赋为undefined
let [foo] = []; // foo = undefined
let [cdc, foo, nsa] = [1]; // cdc = 1,foo = undefined, nas = []
字符串拓展
1.模板字符串
const foo = 'my name is' + exp;
// 等于
const foo = `my name is ${exp}`
2.标签模板
标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。
alert`hello`
// 等同于
alert(['hello'])
函数拓展
默认值
function test(quantity) {
const q = quantity || 1;
return q;
}
// 等于
function test(quantity = 1) {
return quantity;
}
箭头函数
1、函数体内的this是定义时的父级,不是调用时的对象
2、函数体内不存在arguments,用rest替代
3、不能用作构造函数
数组拓展
实例的find()和findIndex()
参数都是回调函数,用于找到第一个符合的成员(成员位置)
[1, 4, -5, 10].find((n) => n < 0)
// -5
实例的keys()和values()
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
拓展运算符
...
let obj1 = { a: 1, b: 2,c: 3 }
let obj2 = { b: 4, c: 5, d: 6}
let merged = {...obj1, ...obj2};
双冒号
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
Symbol
唯一值、魔法字符串、模拟私有变量
1、作为对象的属性key,但不能被.keys和for in遍历
2、作为常量
3、和模块化一起使用模拟私有属性
Set和Map
Set
set
类似于数组,但是成员的值都是唯一的
向 Set 加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值
属性: size
方法: add() delete() has() clear()
weakset
WeakSet的成员只能是对象WeakSet中的对象都是弱引用
Map
map
Object,本质上是键值对的集合。是传统上只能用字符串当作键
const obj = new Map();
obj.set(34, 'asdf');
obj.get(34);
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
weakMap
- 只接受对象作为键名(null除外)
- 它的键名所引用的对象都是弱引用
Promise
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
Generator
*标记一个函数为协程,yield表示执行到此处,执行权将交给其他协程
协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行
Iterator
接口,为JS中表示集合的数据结构提供遍历的操作
原生具有Iterator接口的数据结构:Array、String、Map、Set、arguments、NodeList
function iterator2(arr) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < arr.length ? {value: arr[nextIndex++]} : {done: true}
}
}
}
async/await
generator依靠执行器co,async自带执行器。是对generator的实现
Class
class中方法不可枚举,不存在变量提升
class中子类构造函数调用super