内置类型
js中分为七种内置类型 七种内置类型又分为基本类型和对象类型
基本类型有六种:null undefined boolean string number symbol
其中js中数字类型是浮点型 没有整型 并且浮点型是基于IEEE 754标准实现 在使用过程中会遇到精度不准的bug
NaN也属于number类型 并且NaN不等于自身
对象类型是引用类型 所以会存在浅拷贝和深拷贝的问题
let a = { name: 'hjx' };
let b = a;
b.name = 'yym';
console.log(a.name) //yym
//因为b拷贝的是a对象的引用地址 导致b和a是同一个引用地址 所以b的改变会影响到a
Typeof
typeof 对于基本类型 除了 null 都可以显示正确的类型
typeof 对于对象类型 除了函数都会显示 object
对于 null 来说 虽然它是基本类型 但是会显示 object 这是一个存在很久的 bug
原因:因为在js的最初版本中 使用的是32位系统 为了性能考虑使用低位存储变量的类型信息
000开头代表对象 然而 null 的表示全为零 所以将它错误判断为 object 虽然现在的内部类型代码已经改变了
但是对于这个 bug 却一直流传下来
如果我们想要获取一个变量的正确类型 可以通过Object.prototype.toString.call(x)的方式 这样我们就可以得到类型[object Type]的字符串
类型转换
1. 转Boolean类型
在条件判断时 除了undefined null false '' NaN 0 -0 其他所有的值都转成true 包括所有对象
2. 基本类型转数字类型
false -> 0
true -> 1
null -> 0
undefined -> NaN
'' -> 0
如果字符只包含数字 将其转化为十进制数字 '1' -> 1 '01' -> 1
如果字符包含有效浮点格式 将其转化为对应浮点数值
如果包含有效十六进制格式 '0xf' -> 15
其他字符都转化为NaN 例如 '1h' '1.2.2'
3. 对象转化成基本类型
先调用valueOf 然后调用toString
由于Array重写了toString 这也是为什么[]转换为Number类型为0的原因
4. 加法中的类型转换
在加法中 字符的优先级最高 其次是数字
如果加法运算时存在字符 那么将会把其他所有转化为字符进行运算
1 + '1' + [1] //'111'
这里发现运算中存在字符 把每一项转化为字符然后相加 1 -> '1'
[1] -> '1' 最后结果为'111'
如果加法中不存在字符 存在数字 那么将会把其他所有转化为数字进行运算
如果加法中只存在对象类型 那么将对象转化为基础类型进行运算
[1, 2] + [1, 2] // '1212'
5. 双等中的类型转换
undefined == null //true
双等中数字优先 也就是说如果存在数字 则将另一方转化为数值进行比较
如果双方都为对象类型 则将其转化为基础类型进行比较
原型
每个对象都有__proto__属性 该属性指向创建该对象构造函数的原型prototype
其实这个属性指向的是[[prototype]] 但是[[prototype]]是内部属性 我们不能直接访问
所以使用__proto__来访问
对象可以通过__proto__来寻找不属于该对象上的属性 _proto__将对象连接起来组成原型链
new
new的工作原理
1. 新生成一个对象
2. 链接到原型
3. 绑定this
4. 返回对象
我们可以尝试实现new的功能
function create() {//第一个参数传构造函数 之后为参数
//创建空对象
const obj = {};
const Con = [].shift.call(arguments);
//链接原型
obj.__proto__ = Con.prototype;
//绑定this
Con.apply(obj, arguments);
//返会新对象
return obj;
}
另外new Foo() 的优先级要大于 new Foo
例:
new Foo.getName() 等价于 new (Foo.getName())
new Foo().getName() 等价于 (new Foo()).getName()
instanceof
instanceof的工作原理是看对象的原型链上是否存在该类型的原型
例:[] instanceof Object //true
instanceof 会看[]的原型链上是否存在Object.prototype 如果存在 返会true 否则返回false
接下我们手动实现一个instanceof
function myInstanceof(obj, type) {
prototype = type.prototype;
let proto = obj.__proto__;
while (true) {
if (proto === prototype) {
return true;
}
if (proto === null) {
return false;
}
proto = proto.__proto__
}
}
this
this的指向取决于函数的调用 而与函数的声明无关
直接调用(这里也可以理解成在window作用域中调用)所以this指向window
间接调用 谁调用this便指向谁
执行上下文
执行上下文分三种
全局执行上下文
函数执行上下文
eval执行上下文
执行上下文在代码执行前生成 每个执行上下文中都有三个重要的属性 分别是变量对象VO 活动对象AO(函数执行前产生) 作用域链[[Scope]]
console.log(a); //undefined
var a = 1;
之所以会打印出undefined 就是因为执行上下文在代码执行前生成
globalContext.VO = { a: undefined };
这也是为什么存在声明提升的原因
深浅拷贝
由于给一个变量赋值对象时保存的是对象的引用
const a = { name: 'hjx' };
const b = a;
b.name = 'yym';
console.log(a.name); // 'yym'
所以会导致b的改变会使得a也发生改变 这也导致对象拷贝存在深浅拷贝
浅拷贝
Object.assin() 或 扩展运算符
深拷贝
JSON.parser(JSON.stringify(obj))
但是这个方法存在局限性:
1. 会忽略undefined
2. 不能序列化函数
3. 不能解决循环引用的对象
function deepClone(origin, target) {
for (let key in origin) {
if (origin.hasOwnProterty(key)) {
if (typeof origin[key] === 'object' && origin[key] !== null) {
target[key] = Array.isArray(origin[key]) ? [] : {};
deepClone(origin[key], target[key]);
}else {
target[key] = origin[key];
}
}
}
}
模块化
1. es6模块化
在有babel的情况下 我们可以直接使用es6的模块化
//a.js
export const obj = { name: 'hjx' };
export function foo() {}
//b.js
export default function () {}
import { obj, foo } from 'a.js'
import xxx from 'b.js'
2. CommonJS
CommonJS是node独有的规范 浏览器中使用就需要用到Browserify解析了
// a.js
module.exports = { a: 1 }
//or
exports.a = 1
//b.js
const module = require('./a.js');
module.a // 1
3. AMD
AMD是由RequireJS提出的 我没用过
防抖
function debounce(handler, delay) {
let timer = null;
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
handler.call(this);
}, delay)
}
}
节流
function throttle(handler, wait) {
let startTime = 0;
return function () {
let endTime = new Date().getTime();
if (endTime - startTime > wait) {
handler.call(this);
}
startTime = endTime;
}
}
继承
Sub.prototype = Object.create(Super.prototype, {
constructor: {
value: Sub,
enumerable: false,
writeable: true,
configurable: true
}
})
//or
function inherit(Origin, Target) {
function F() {};
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
}
call apply bind
call 和 apply 都是用来改变this指向 唯一不同点就是传参方式不同
1. 模拟实现call
Function.prototype.myCall = function (context, ...arg) {
context = context || window;
context.fn = this;
const result = context.fn(...arg);
delete context.fn;
return result;
}
2. 模拟实现apply
Function.prototype.myApply = function (context, ...arg) {
context = context || window;
context.fn = this;
const result = context.fn(arg);
delete context.fn;
return result;
}
3. 模拟实现bind
Function.prototype.myBind = function (context, ...arg) {
const originFn = this;
return function F() {
if (this instanceof F) {
//因为返回的是函数 所以可能存在new的情况
return new originFn(...arg, ...arguments);
}
return originFn.apply(context, arg.concat(arguments));
}
}