基础
六个基本类型 ***null***、***undefined***、**boolean**、**number**、**string**、**symbol**
一个引用类型 **Object**
number类型是浮点类型,没有整数,NaN也属于number类型,并且NaN不等于自身
let s1 = Symbol()
let s2 = Symbol()
let s3 = Symbol('a')
let s4 = Symbol('a')
s1 !== s2
s3 !== s4
String(Symbol())
Boolean(Symbol())
let mySymbol = Symbol()
let a = {}
Object.defineProperty(a, mySymbol, { value: 1 })
let s1 = Symbol.for('a')
let s2 = Symbol.for('a')
s1 === s2
let s1 = Symbol.for('a')
Symbol.keyFor(s1)
let s2 = Symbol('a')
Symbol.keyFor(s2)
typeof 对于基本类型,除了null都可以显示正确得类型
typeof 1
typeof '1'
typeof undefined
typeof true
typeof Symbol()
typeof b
typeof null
# 因为在JS的最初版本中,使用的是32位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来
# typeof 对于对象,除了函数都会显示object
typeof []
typeof {}
typeof console.log
# 如果我们想正确得获取一个变量得类型,可以通过 Object.prototype.toString.call(xx)。获得类似 [object Type] 得字符串
Object.prototype.toString.call(1)
'[object Number]'
Object.prototype.toString.call('1')
'[object String]'
Object.prototype.toString.call(true)
'[object Boolean]'
Object.prototype.toString.call(null)
'[object Null]'
Object.prototype.toString.call(undefined)
'[object Undefined]'
Object.prototype.toString.call(Symbol('a'))
'[object Symbol]'
Object.prototype.toString.call([])
'[object Array]'
Object.prototype.toString.call({})
'[object Object]'
Object.prototype.toString.call(console.log)
'[object Function]'
# instanceof可以正确判断对象得类型,因为内部机制是通过判断对象得原型链中是不是能找到类型得prototype
function instanceof(left, right) {
let prototype = right.prototype
left = left.__proto__
while (true) {
if (left === null)
return false
if (prototype === left)
return true
left = left.__proto__
}
}
# 转Boolean,除了undefined、null、false、NaN、0、-0、''、其他的所有值都转为true,包括对象
# 对象转基本类型,会先调用valueOf,然后调用toString。并且这两个方法可以重写
# 也可以重写 [Symbol.toPrimitive] () {},该方法在转基本类型时调用优先级最高
# 加法一方字符串,另一方隐式转换为字符串,其他运算一方是数字,那么另一方隐式转换为数字。
# 并且加法会触发三种类型转换:值转换为原始值、转换为数字Number(x)、转换为字符串。
1 + '1'
2 * '2'
[1, 2] + [2, 1]
# [1, 2].toString() -> '1,2'
# [2, 1].toString() -> '2,1'
# '1,2' + '2,1' = '1,22,1'
'a' + + 'b'
+ 'b'
+ '1'
若Type(x)与Type(y)相同,则
为Undefined、null返回true
为Number,有NaN就返回false,-0==0,比较值
为String,完全相同字符序列返回true
为Boolean,同为true或者false返回true
为对象,看是否是引用的同一个
不同,则
null == undefined
一个为Number,另一个为String,String转Number比较
有一个为Boolean,Boolean转Number比较
一个为Number或String,另一个为Object,Object转基本类型比较
# 在ES标准中是 [[Prototype]],谷歌浏览器实现就是将它命名为__proto__
# __proto__ 和 constructor属性是对象所独有的
# prototype是函数所独有的,所以函数有 prototype 和 __proto__ 和 constructor
# __proto__由一个对象指向他的原型对象,当访问一个对象的属性如果不存在的时候,就会去__proto__指向的对象里找,一直往上,最后null为终点,即原型链
# prototype含义是函数的原型对象,也是这个函数所创建的实例的原型对象,让所有实例化的对象们都能找到公用的属性和方法,任何函数在创建的时候默认同时创建该函数的prototype对象
# constructor构造函数,重点就是Function这个函数
# ES5实现思路是将子类原型设置为父类的原型
function Super() {}
Super.prototype.getNumber = function() {
return 1
}
function Sub() {}
let s = new Sub()
Sub.prototype = Object.create(Super.prototype, {
constructor: {
value: Sub,
enumerable: false,
writable: true,
configurable: true
}
})
# ES6使用class语法
class MyDate extends Date {
test() {
return this.getTime()
}
}
let myDate = new MyDate()
myDate.test()
# 报错因为ES6不是所有浏览器都兼容,需要使用Babel编译,而且JS底层有限制,如果不是由Date构造出来的实例,不能调用Date的函数,侧面说出ES6的class继承和ES5的继承不同
# 改变思路实现继承
function MyData() {}
MyData.prototype.test = function () {
return this.getTime()
}
let d = new Date()
Object.setPrototypeOf(MyData.prototype, Date.prototype)
Object.setPrototypeOf(d, MyData.prototype)
# 1、函数被调用
# 2、新生成了一个对象
# 3、链接到原型
# 4、绑定 this
# 5、返回新对象的引用
function create() {
let obj = new Object()
let Con = [].shift.call(arguments)
obj.__proto__ = Con.prototype
let result = Con.apply(obj, arguments)
return typeof result === 'object' ? result : obj
}
# 严格模式下是undefined
# call(绑定对象, 参1, 参2 ...)
# apply(绑定对象, [参1, 参2 ...])
# bind(绑定对象, 参1, 参2 ...)
# bind创建新对象永久绑定。
function foo() {
console.log(this.a)
}
var a = 1
foo()
var obj = {
a: 2,
foo: foo
}
obj.foo()
var c = new foo()
c.a = 3
console.log(c.a)
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
# 箭头函数没有this,取决于外面的第一个不是箭头函数得this,指向window
# call、apply临时改变this的指向,bind创建一个新对象永久绑定
# call传参数列表,apply传参数数组,bind传参数列表
Function.prototype.myCall = function (context) {
var context = context || window;
# 给context添加一个属性
# getValue.myCall(a, 'yck', '24') => a.fn = getValue
# 这里 this 指向 getValue 这个fn
context.fn = this;
# 将 context 后面的参数取出来
var args = [...arguments].slice(1);
# getValue.call(a, 'yck', '24') => a.fn('yck', '24')
var result = context.fn(...args);
# 删除 fn
delete context.fn;
return result;
}
Function.prototype.myApply = function (context) {
var context = context || window;
context.fn = this;
var result;
# 需要判断是否存在第二个参数
# 如果存在,就将第二个参数展开
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
delete context.fn;
return result;
}
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error');
}
var _this = this;
var args = [...arguments].slice(1);
# 返回一个函数
return function F() {
# 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
全局执行上下文
函数执行上下文
eval执行上下文
变量对象(VO),包含变量、函数声明和函数得形参,该属性只能在全局上下文中访问
作用域链
this
var a = 10
function foo(i) {
var b = 20
}
foo()
stack = [
globalContext,
fooContext
]
globalContext.VO === globe
globalContext.VO = {
a: undefined,
foo: <Function>
}
fooContext.VO === foo.AO
fooContext.AO = {
i: undefined,
b: undefined,
arguments: <>
}
fooContext.[[Scope]] = [
globalContext.VO
]
fooContext.Scope = fooContext.[[Scope]] + fooContext.VO
fooContext.Scope = [
globalContext.VO,
fooContext.VO
]
创建阶段。创建VO,JS解释器会找出需要提升得变量和函数,提前在内存中开辟好空间,函数是将整个函数存入内存中,变量是只声明赋值undefined
执行阶段。
for ( var i=1
setTimeout( function timer() {
console.log( i )
}, i*1000 )
}
for (var i = 1
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
for ( var i=1
setTimeout( function timer(j) {
console.log( j )
}, i*1000, i)
}
{ // 形成块级作用域
let i = 0
{
let ii = i
setTimeout( function timer() {
console.log( ii )
}, i*1000 )
}
i++
{
let ii = i
}
i++
{
let ii = i
}
...
}
# 对象是保存的引用地址
# 浅拷贝可以通过a = Object.assign({}, b) 把b可枚举的属性不包括继承的替换第一个参数、扩展运算符来解决。
# 但是遇到层级比较深或者对象套对象,就需要深拷贝JSON.parse(JSON.stringify(a)),但是有局限性:
会忽略undefined
会忽略Symbol
不能序列化函数
不能解决循环引用的对象
# 建议深拷贝使用lodash的深拷贝函数、a = Object.create(b)原型链继承或自己封装。
function checkType(any) {
return Object.prototype.toString.call(any).slice(8, -1)
}
function clone(any){
if(checkType(any) === 'Object') {
let o = {};
for(let key in any) {
o[key] = clone(any[key])
}
return o;
} else if(checkType(any) === 'Array') {
var arr = []
for(let i = 0, leng = any.length; i<leng; i++) {
arr[i] = clone(any[i])
}
return arr;
} else if(checkType(any) === 'Function') {
return new Function('return '+any.toString()).call(this);
} else if(checkType(any) === 'Date') {
return new Date(any.valueOf());
} else if(checkType(any) === 'RegExp') {
return new RegExp(any);
} else if(checkType(any) === 'Map') {
let m = new Map()
any.forEach((v,k)=>{
m.set(k, clone(v))
})
return m;
} else if(checkType(any) === 'Set') {
let s = new Set()
for(let val of any.values()) {
s.add(clone(val))
}
return s;
}
return any;
}
AMD是由RequireJS提出的。
define(['./a', './b'], function(a, b) {
a.do()
b.do()
})
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
var b = require('./b')
b.doSomething()
})
# CommonJS 是Node独有的规范,浏览器使用需要用到Browserify解析。
module.exports = {
a: 1
}
exports.a = 1
# 底层是 exports = module.exports,所以exports只能给属性添加值,不能直接赋值
var module = require('./a.js')
# CommonJS和ES6模块化区别
CommonJS支持动态导入,require(${path}/xx.js)
CommonJS用于服务器,同步。ES6模块化用于浏览器,异步。
CommonJS导出是值拷贝,导出值改变了,导入值不会改变。ES6是导入导出值指向同一个内存地址,所以导入值会跟着导出值变化。
ES6模块化会编译成require/exports来执行。
export function a () {}
export const b = 555
# 只能有一个export default,引入可取名
export default function () {}
# 可用as重命名
import {a as a1, b} from './test.js'
import xxx from './test.js'
# V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制。因此,V8 将内存(堆)分为新生代和老生代两部分。
新生代算法
新生代中得对象一般存活时间较短,使用Scavenge GC算法
内存空间分为From和To空间,必定有一个空间是使用一个是闲置得,新分配的对象放入From空间,当From被占满时,新生代GC启动,算法检查From空间存活的复制到To空间,失活得销毁,From和To空间互换。
老生代算法
老生代中得对象一般存活时间较长且数量多,使用两个算法:标记清除和标记压缩
什么情况下对象会出现在老生代空间:
新生代中对象经历过一次Scavenge算法,就会从新生代空间移到老生代空间
To空间得对象占比超过25%,会将这个对象移到老生代空间
老生代空间很复杂,有如下几个空间
enum AllocationSpace {
RO_SPACE,
NEW_SPACE,
OLD_SPACE,
CODE_SPACE,
MAP_SPACE,
LO_SPACE,
NEW_LO_SPACE,
FIRST_SPACE = RO_SPACE,
LAST_SPACE = NEW_LO_SPACE,
FIRST_GROWABLE_PAGED_SPACE = OLD_SPACE,
LAST_GROWABLE_PAGED_SPACE = MAP_SPACE
};
在老生代中,以下情况会先启动标记清除算法:
某一个空间没有分块的时候
空间中被对象超过一定限制
空间不能保证新生代中的对象移动到老生代中
在这个阶段中,会遍历堆中所有的对象,标记活的对象,销毁所有没有被标记的对象。在标记大型堆内存时,可能需要几百毫秒才能完成一次。会导致一些性能上的问题。
为了解决这个问题:
2011 年,V8 从 stop-the-world 标记切换到增量标志。在增量标记期间,标记分解为更小的模块,可以让 JS 在模块间隙执行,从而不至于让应用出现停顿情况。
2018 年,改为并发标记。可以让 GC 扫描和标记对象时,同时允许 JS 运行。
清除对象后会造成堆内存出现碎片的情况,当碎片超过一定限制后会启动压缩算法,将活的对象像一端移动,直到所有对象都移动完成然后清理掉不需要的内存。
提升
const PENDING = "pending"
const RESOLVED = "resolved"
const REJECTED = "rejected"
function MyPromise(fn) {
let _this = this
_this.currentState = PENDING
_this.value = undefined
_this.resolvedCallbacks = []
_this.rejectedCallbacks = []
_this.resolve = function (value) {
if (value instanceof MyPromise) {
return value.then(_this.resolve, _this.reject)
}
setTimeout(() => { // 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = RESOLVED
_this.value = value
_this.resolvedCallbacks.forEach(cb => cb())
}
})
}
_this.reject = function (reason) {
setTimeout(() => { // 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = REJECTED
_this.value = reason
_this.rejectedCallbacks.forEach(cb => cb())
}
})
}
try {
fn(_this.resolve, _this.reject)
} catch (e) {
_this.reject(e)
}
}
MyPromise.prototype.then = function (onResolved, onRejected) {
var self = this
// 规范 2.2.7,then 必须返回一个新的 promise
var promise2
// 规范 2.2.onResolved 和 onRejected 都为可选参数
// 如果类型不是函数需要忽略,同时也实现了透传 Promise.resolve(4).then().then((value) => console.log(value))
onResolved = typeof onResolved === 'function' ? onResolved : v => v
onRejected = typeof onRejected === 'function' ? onRejected : r => throw r
if (self.currentState === RESOLVED) {
return (promise2 = new MyPromise(function (resolve, reject) {
// 规范 2.2.4,保证 onFulfilled,onRjected 异步执行
// 所以用了 setTimeout 包裹下
setTimeout(function () {
try {
var x = onResolved(self.value)
resolutionProcedure(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
}))
}
if (self.currentState === REJECTED) {
return (promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
// 异步执行onRejected
try {
var x = onRejected(self.value)
resolutionProcedure(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
}))
}
if (self.currentState === PENDING) {
return (promise2 = new MyPromise(function (resolve, reject) {
self.resolvedCallbacks.push(function () {
// 考虑到可能会有报错,所以使用 try/catch 包裹
try {
var x = onResolved(self.value)
resolutionProcedure(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
self.rejectedCallbacks.push(function () {
try {
var x = onRejected(self.value)
resolutionProcedure(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
}))
}
}
function resolutionProcedure(promise2, x, resolve, reject) {
// 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
if (promise2 === x) {
return reject(new TypeError("Error"))
}
// 规范 2.3.2
// 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
if (x instanceof MyPromise) {
if (x.currentState === PENDING) {
x.then(function (value) {
// 再次调用该函数是为了确认 x resolve 的
// 参数是什么类型,如果是基本类型就再次 resolve
// 把值传给下个 then
resolutionProcedure(promise2, value, resolve, reject)
}, reject)
} else {
x.then(resolve, reject)
}
return
}
// 规范 2.3.3.3.3
// reject 或者 resolve 其中一个执行过得话,忽略其他的
let called = false
// 规范 2.3.3,判断 x 是否为对象或者函数
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 规范 2.3.3.2,如果不能取出 then,就 reject
try {
// 规范 2.3.3.1
let then = x.then
// 如果 then 是函数,调用 x.then
if (typeof then === "function") {
// 规范 2.3.3.3
then.call(
x,
y => {
if (called) return
called = true
// 规范 2.3.3.3.1
resolutionProcedure(promise2, y, resolve, reject)
},
e => {
if (called) return
called = true
reject(e)
}
)
} else {
// 规范 2.3.3.4
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
// 规范 2.3.4,x 为基本类型
resolve(x)
}
}
# 使用 * 表示这是一个 Generator 函数
# 内部可以通过 yield 暂停代码
# 通过调用 next 恢复执行,value是yield后面的返回值
function* test() {
let a = 1 + 2;
yield 2;
yield 3;
}
let b = test();
console.log(b.next());
console.log(b.next());
console.log(b.next());
# 是Generator函数的语法糖
# async 声明一个异步函数,返回Promise对象
# await只能在async函数中使用,暂停异步得功能执行,后面需要一个Promise对象如果不是则会被转成Promise对象,只要有一个Promise对象reject了,整个async函数都会中断,如果resolve,返回值就是.then得参数。
# 最好把await放到tryCatch中
# 如果发出三个互不依赖得请求可以
let results = await Promise.all([getA, getB, getC])
# Map 作用是生成一个新数组,遍历原数组,三个参数,分别是当前索引元素,索引,原数组,将每个元素拿出来做一些变换然后 append 到新的数组中。
[1, 2, 3].map((v) => v + 1)
# FlatMap 和 map 的作用几乎是相同的,但是对于多维数组来说,会将原数组降维。可以将 FlatMap 看成是 map + flatten ,目前该函数在浏览器中还不支持。
[1, [2], 3].flatMap((v) => v + 1)
arr.reduce(function (prev, cur, index, arr) {
return prev + cur
},[initialValue])
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver)
},
set(target, property, value, receiver) {
setBind(value)
return Reflect.set(target, property, value)
}
}
return new Proxy(obj, handler)
}
let obj = { a: 1 }
let value
let p = onWatch(
obj,
(v) => {
value = v
},
(target, property) => {
console.log(`Get '${property}' = ${target[property]}`)
}
)
p.a = 2 // value: 2、 obj = {a: 2}
p.a // -> Get 'a' = 2
!function(n){
var e=n.document,
t=e.documentElement,
i=720,
// 设计稿的宽度/rem换算比例
d=i/100,
o="orientationchange"in n?"orientationchange":"resize",
a=function(){
var n=t.clientWidth||320
// 改变根节点html的大小
t.style.fontSize=n/d+"px"
}
e.addEventListener&&(n.addEventListener(o,a,!1),e.addEventListener("DOMContentLoaded",a,!1))
}(window)
window.onload = function(){
/*
720设计稿的宽度
100换算比例,比如宽度是100px,就可以写为1rem
*/
getRem(720,100)
}
window.onresize = function(){
getRem(720,100)
}
function getRem(pwidth,prem){
var html = document.getElementsByTagName("html")[0]
var oWidth = document.body.clientWidth || document.documentElement.clientWidth
html.style.fontSize = oWidth/pwidth*prem + "px"
}
# 因为JS采用IEEE754双精度版本(64位),所有采用IEEE754都有该问题
# 计算机表示十进制是采用二进制
parseFloat((0.1 + 0.2).toFixed(10))