一、类型
1、基础类型和引用类型
- 基础类型有:
number
string
boolean
null
undefined
symbol
bigint
- 引用类型有:
object
array
function
...
2、类型检测
typeOf 和 instanceOf
- typeOf可以判断基础类型、判断对象类型时,除了函数 其他都是
object
- instanceOf 可以判断对象类型,原理是通过原型链查找原型对象
Object.prototype.toString.call()
instanceOf 实现手写
const myInstanceOf = (a, A) {
if(typeOf a !== 'object') {
return false
}
if(a._proto_ === A.prototype) {
return true
}
return myInstanceOf(a._proto_, A)
}
3、类型转换
- == 的转换只有三种:转成数字、转成字符串、转成bealoon
4、对象转原始类型的流程
- 先找Symbol.toPrimitive方法, 优先调用
- 再找valueOf方法,如果转为原始类型,则返回。
- 调用toString(), 如果转为原始类型则返回
- 如果都没有则报错
二、闭包
作用域链
什么是闭包
闭包是指有权访问另外一个作用域中变量的函数。
为什么会有闭包
作用域有 全局作用域和函数作用域。内层函数会将拷贝外层的作用域,与自己的作用域一起形成作用域链。作用域链链保证了执行环境对变量的有序访问,当在当前环境内没有找到的变量,就会去父级作用域查找
三、原型链
如何理解原型链
- 构造函数有一个prototype属性,指向了原型对象。
- 原型对象中的属性和方法会被构造函数的所有实例共享。
- 实例对象有一个_proto_属性指向了原型对象
- 原型对象又有自己的_proto_指向它的原型对象,因此就形成了原型链
四、继承
- 实现继承的几种方式(逐步趋近完善)
1、借用call
function Parent(){
this.name = `parent1`;
}
function Child() {
Parent.call(this)
this.type = 'child'
}
缺点:没办法继承父类的方法
2、借用原型链
function Parent(){
this.name = `parent1`;
this.play = [1,2,3]
}
function Child() {
this.type = 'child'
}
Child.prototype = new Parent()
缺点:继承的属性会被所有实例共享
3、call 和原型链组合
function Parent(){
this.name = `parent1`;
this.play = [1,2,3]
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = new Parent()
缺点: Parent的构造函数会被重复调用。
4、组合方式优化
function Parent(){
this.name = `parent1`;
this.play = [1,2,3]
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = Parent.prototype
缺点: Child.prototype.constructor 指向了Parent
5、寄生组合(完美方案)
function Parent(){
this.name = `parent1`;
this.play = [1,2,3]
}
function Child() {
Parent.call(this)
this.type = 'child'
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
五、函数的arguments、this
arguments
1. arguments 是不是数组
不是数组、是一个类数组对象。它的key从0开始依次递增。并且有length属性,但是没有数组的诸多工具方法
2. 如何转成数组
1. 通过Array.form
2. 通过结构赋值 [...arguments]
3. Array.prototype.slice.call(arguments)
4. Array.prototype.concat.apply([], arguments)
this 的指向
各种场景下的this指向
- 全局上下问的
this
指向window
,严格模式下为undefined
- 直接调用函数与全局上下文一致
对象.方法
的方式,this
指向这个对象- DOM事件绑定:
onclick
和addEventerListener
中this
指向绑定事件的元素 IE浏览器中的attachEvent,this
指向window - new + 构造函数,构造函数中的
this
指向实例对象。 - 箭头函数没有自己的
this
,也不能绑定,箭头函数中的this
指向构造时上下文环境的this
手动实现new
- 创建一个空对象
obj
- appy一下构造函数
- 将
obj
的_proto_
指向构造函数的prototype
- 如果构造函数的执行结果返回一个对象,则返回该对象、否则返回
obj
function myNew(cstor, ...args) {
let obj = {}
let res = cstor.apply(obj, args)
if(typeOf res === 'object' || typeOf res === 'function') {
return res
}
obj.__proto__ = Object.create(cstor.prototype)
return obj
}
实现apply、call
- ES5的方式
// 以call为例
Function.prototype.call = function(context) {
// 不能用 var args = arguments.slice(1) ,因为arguments 不是数组对象
var args = []
for(i = 1; i < arguments.length; i ++) {
args.push(arguments[i])
}
context.fn = this
// 通过数组转字符串来提取参数
var res = eval('context.fn(' + arg + ')')
delete context.fn
return res
}
- ES6的方式
// apply
Function.prototype.myApply(obj, args) {
let fn = this
obj.fn = fn
// !数组里面的参数通过解构传进来
let res = obj.fn(...args)
delete obj.fn
return res
}
// call
Function.prototype.myApply(obj, ...args) {
let fn = this
obj.fn = fn
let res = obj.fn(...args)
delete obj.fn
return res
}
手动实现bind
- bind 会返回一个新函数
- 如果新函数被作为构造函数执行时,忽略this绑定。
Function.prototype.bind = function(context, ...bindArg) {
let fn = this
// 闭包
let resFn = function(...arg) {
if (this instanceof resFn) {
// 作为构造函数被调用
return fn.call(this)
} else {
return fn.call(context, ...bindArg.concat(arg)))
}
}
// 原函数的原型对象不能丢
resFn.prototype = Object.create(this.prototype)
return resFn
}
六、 数组
##1、判断数组中包含某值
- includes
- indexOf
- find
- findIndexOf
- some
2、数组扁平化
let arr = [ [1,3,[12]] ,5]
- flat 方法
arr.flat(Infinity)
- replace + 正则匹配
let str = JSON.stringify(arr)
str.replace(/(\[ | \])/, '').split(',')
// 除了split, 也可以 在外面加上[] 字符之后通过JSON.parse 转成数组
- 递归
// 结合reduce
function myFlat(arr) {
return arr.reduce((res, item) => {
if(Array.isArray(item)) {
return res.concat(myFlat(item))
} else {
return res.concat(item)
}
}, [])
}
// 也可以用forEach
- some + concat + 扩展运算符号
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
延伸:对象扁平化
// 例:有对象如下
{
a: 1,
b: { ba: 2, bb: 4},
c: [ 1 , 3],
}
// 扁平化转成
{
a: 1,
b.ba: 2,
b.bb:4,
c.0: 1,
c.2: 3
}
实现如下:利用递归
const objectFlat = function(originObj) {
if(!originObj) {
return originObj
}
let result = {}
function flat(obj, prefixKey, res) {
for(key in obj) {
let currentKey = prefixKey ? `${prefixKey}.${key}` : key
if(typeof obj[key] === 'object' && obj[key] !== null) {
// 如果是数组或对象
flat(obj[key], currentKey , res)
} else {
res[currentKey] = obj[key]
}
}
}
flat(originObj, '', result)
return result
}
3、数组的函数手动实现
- map
Array.prototype.map = function(callback, thisObj) {
// 边界处理
if(this === null || this === undefined) {
throw new Error("can not read prototype 'map' of null or undefind ")
}
let currentArr = this
let len = currentArr.length
let res = []
for(let i = 0; i < len; i ++) {
res.push(callback.call(thisObj, currentArr[i], currentArr, thisObj))
}
return res
}
- reduce
Array.prototype.reduce = function(callback, initVal) {
// 校验入参,具体同上
CHACK_ARG(callback, initVal)
let len = this.length
let res = initVal || this[0]
for(let i = initVal ? 0 : 1 ; i < len ; i++) {
res = callback(res, this[i], this)
}
return res
}
- filter
Array.prototype.fliter = function(callback) {
// 校验入参
CHECK_ARG(callback, this)
let currentArg = this
let len = this.length
let res = []
for(let i = 0; i < len ; i++) {
if(callback(currentArg[i])) {
res.push(currentArg[i])
}
}
}
- push、pop
Array.prototype.push = function(...args) {
let arr = this
let len = this.length
let addCount = args.length
// 边界校验:
if(len + addCount > 2 ** 53 - 1){
throwTypeError("The number of array is over the max value restricted!")
}
for(let i = 0; i < addCount; i ++) {
arr[len + i] = args[i]
}
return arr.length
}
Array.prototype.pop = function() {
let arr = this
let len = this.length
if(len === 0) {
return undefined
}
result = arr[len - 1]
delete arr[len - 1]
arr.length = len - 1
return result
}
- splice
// 实现 splice方法
Array.prototype.splice = function(position, count, ...items) {
// 判断数组是否是密封对象或冻结对像
isSealedOrFrozen(this)
let arr = this
let len = this.length
// 处理起始位置,将负数的转成下标
let startIndex = countStartIndex(position, len)
// 处理删除值
let deleteCount = countDeleteCount(startIndex, arr , count)
// 删除需要删除的元素,并返回被删除的元素数组
let deletedArr = deleteArrItems(arr, startIndex, count)
// 移动数组元素
moveArrItem(arr, startIndex, deleteCount, items)
// 插入新元素
insertNewItems(arr, items, startIndex)
return deletedArr
}
function isSealedOrFrozen(arr, deleteCount, addItems) {
if(Object.isSealed(arr) && deleteCount !== addItems.length) {
// ! 密封对象不可以删除或新增属性,但可以修改已有属性,所以当新增和删除相同时,不会有问题
throw new TypeError('this array is a sealed Object')
} else if(Object.isFrozen(arr) && (deleteCount > 0 || addItems.length > 0) ) {
// ! 冻结对象不可删除、不可新增、不可更改
throw new TypeError('this array is a frozen object')
}
}
function countStartIndex(position, arrLen) {
if(parseInt(position) !== position) {
throw new Error('参数类型错误')
}
if(position < 0) {
return arrLen + position
}
return position
}
function countDeleteCount(startIndex, arr, count) {
if(parseInt(count) !== count) {
throw new Error('count 参数类型错误')
}
if(count < 0) {
return 0
}
if(count > arr.length - startIndex) {
return arr.length - startIndex
}
return count
}
function deleteArrItems(arr, startIndex, count) {
// 删除元素,但暂不挪动位置
let deleted = []
if(!count) {
return deleted
}
for(let i = startIndex; i < startIndex + count ; i ++) {
deleted.push(arr[i])
delete arr[i]
}
return deleted
}
function moveArrItem(arr, startIndex, count, items) {
let arrLen = arr.length
let itemsLen = items.length
if(itemsLen === count) {
return
}
if(itemsLen > count) {
// 添加数大于删除数整体后移
let moveLen = itemsLen - count
for(let i = arrLen - 1; i <= startIndex; i --) {
arr[i + moveLen] = arr[i]
}
}
if(itemsLen < count) {
let moveLen = count - itemsLen
for(let i = startIndex + count ; i < arrLen; i ++) {
arr[i - moveLen] = arr[i]
}
}
}
function insertNewItems(arr, items, startIndex) {
//
let arrLen = arr.length
let itemsLen = items.length
for(let i = startIndex; i < startIndex + itemsLen; i ++) {
arr[i] = items[i - startIndex]
}
}
- sort
- 当对比函数返回值大于0, 则a 在b 后面,即a 的下标应该比b大;
- 反之,则a 在b 的前面,即a 的下标比b小;
- 整个过程是完成一次升序的排列。
// 使用范例:
let nums = [2,3,1]
nums.sort(function(a, b) {
if(a > b) return 1;
else if(a < b) return -1
else if(a === b) return 0
})
手写实现:
源码中的sort思路 :
n <= 10
时,采用插入排序
n > 10
时,采用三路快速排序10 < n <= 1000
,采用中位数作为哨兵元素n > 1000
每隔200 ~215个元素挑一个元素,放到新数组,然后对它进行排序,然后找到中间数,作为哨兵元素。
Array.prototype.sort = function (compareFn) {
const len = arr.length
// 处理参数边界值
const arr = handleParams(this, compareFn)
return quickSort(arr, compareFn)
}
function handleParams(arr, fn) { // 处理参数边界值
if (typeof fn !== 'function' && fn !== 'undefined') {
throw new TypeError('fn is not a function!')
} else if (fn === 'undefined') {
return arr.map((item) => {
return `${item}`
})
} else {
return arr
}
}
function insertSort(arr, compareFn) {
// 插入排序
const len = arr.length
for (let i = 0; i < len; i++) {
const current = arr[i]
// 从当前元素往前找,如果与前面的元素比较结果 < 0, 则将当前元素插入到它前面
for (let j = i; j >= 0; j--) {
if (compareFn(current, arr[i]) < 0) {
arr[i + 1] = arr[i]
arr[i] = current
}
}
}
}
function quickSort(arr, compareFn) {
const len = arr.length
if (len <= 10) {
// 如果数组太短,自动改用插入排序
return insertSort(arr, compareFn)
}
const sentry = getSentry(arr, compareFn)
// 三路快排
const low = []
const high = []
const eq = [sentry]
for (let i = 0; i < len; i++) {
if (compareFn(arr[i], sentry) < 0) {
low.push(arr[i])
} else if (compareFn(arr[i], sentry) > 0) {
high.push(arr[i])
} else {
eq.push(arr[i])
}
}
return [...quickSort(low, compareFn), ...eq, ...quickSort(high, compareFn)]
}
function getSentry(arr, compareFn) { // 计算哨兵元素
const len = arr.length
if (len <= 10) {
// 如果长度小于10 不需要哨兵元素
return 0
}
if (len > 10 && len <= 1000) {
// 直接取中间点
const sentryIndex = Math.floor(len / 2)
return arr[sentryIndex]
}
// 超过1000 每隔200元素挑一个元素,放到新数组,然后对它进行排序,然后找到中间数,作为哨兵元素
const sentryArr = [] // 用来存放备选的哨兵
const sentryArrLen = Math.floor(len / 200) // 计算备选哨兵个数
for (let i = 0; i < sentryArrLen; i++) {
sentryArr.push(arr[200 * i])
}
const sortedSentryArr = quickSort(sentryArr, compareFn)
const middleIndex = Math.floor(sortedSentryArr.length / 2)
return sortedSentryArr[middleIndex]
}
拓展:其他的排序算法
1、冒泡排序
function bubbleSort(arr) {
let len = arr.length
for(let i = 0; i < len; i++) {
for(let j = 0; j < len - 1 - i; j++) {
if(arr[j] > arr[j + 1]) {
let temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
return arr
}
2、归并排序
function mergeSort(arr) {
function sort(arr, start, end) {
const len = end - start
if (len <= 1) {
return arr
}
const middleIndex = Math.floor(len / 2)
const left = sort(arr, start, middleIndex)
const right = sort(arr, middleIndex, end)
const result = []
let p1 = 0
let p2 = 0
while (p1 < left.length && p2 < right.length) {
if (p1 < p2) {
result.push(left[p1])
p1++
} else {
result.push(right[p2])
p2++
}
}
while (p1 < left.length) {
result.push(left[p1])
p1++
}
while (p2 < right.length) {
result.push(right[p2])
p2++
}
return result
}
return sort(arr, 0, arr.length)
}
4、如何跳出forEach
- 使用try-catch,并在需要抛出跳出循环的地方抛出错误
- 推荐使用every 或 some 替代forEach,every在
return false
时中止循环,some 在return true
时跳出循环。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!