1、Object.create()
方法一
Object.prototype.myCreate = function(proto, propertiesObject=undefined){
if( typeof proto!== 'object' && typeof proto!=='function' ){
throw new TypeError(`The passed in prototype must be an object or null: ${proto}`)
}
if( propertiesObject === null ){
throw new TypeError(`Cannot convert undefined or null to object`)
}
const newObj = {}
Object.setPrototypeOf(newObj, proto) // 使用Object.setPrototypeOf()方法设置原型
if( typeof propertiesObject != 'undefined' ){
Object.defineProperties(newObj, propertiesObject)
}
return newObj
}
方法二
我们使用构造函数实现一下
Object.prototype.myCreate = function(proto, propertiesObject=undefined){
if( typeof proto!== 'object' && typeof proto!=='function' ){
throw new TypeError(`The passed in prototype must be an object or null: ${proto}`)
}
if( propertiesObject === null ){
throw new TypeError(`Cannot convert undefined or null to object`)
}
function fn(){};
fn.prototype = proto;
fn.prototype.constructor = fn
if( typeof propertiesObject != undefined ){
Object.defineProperties(fn, propertiesObject)
}
return new fn()
}
检验:
2、instanceof
/*
* @param {Object} obj 需要判断的数据
* @param {Object} constructor 这里请注意
* @return {Boolean}
**/
function myInstanceof(obj, constructor){
if (!['function', 'object'].includes(typeof obj) || obj === null) return false
let objProto = Object.getPrototypeOf(obj)
while(true){
if(!objProto) return false
// 将 对象的隐式原型 和 构造函数的显式原型 比较
if(objProto === constructor.prototype) return true
objProto = Object.getPrototypeOf(objProto)
}
}
3、new
/*
* @param {Function} fn 构造函数
* @return {*}
**/
function myNew(fn, ...args){
if(typeof fn !== 'function'){
return new TypeError('fn must be a function')
}
// 先创建一个对象
let obj = Object.create(fn.prototype)
// 通过apply让this指向obj, 并调用执行构造函数
let res = fn.apply(obj, args)
return (res instanceof Object) ? res : obj
}
4、类型判断
function getType(value){
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
}
5、防抖节流
防抖
防抖是指,短时间频繁触发fn,只执行最后一次,这样可以有效提高性能,减少服务器压力
也可以这样表述:函数在 n 秒后再执行,如果 n 秒内被触发,重新计时,保证最后一次触发事件 n 秒后才执行。
/*
* @param {Function} callback 回调函数
* @param {Number} delay 回调函数在delay ms 后执行
* @param {Boolean} immediate 是否立即执行回调
**/
function debounce(callback, delay=300, immediate=false){
let time = null
// 返回一个闭包
return (...args) => {
// 存在定时器则清空, 重新计时
if(timer){
clearInterval(timer)
timer=null
}
// 若不立即执行
if(!immediate){
timer = setTimeout(() => {
callback.apply(this, args)
}, delay)
}else{
// 立即执行
// timer 为空则代表此时不在delay中, 可以立即执行
let flag = !timer
flag && callback.apply(this, args)
timer = setTimeout(() => {
timer = null
}, delay)
}
}
}
简单版:
function debounce(callback, delay=300){
let timer = null
return (...args) => {
if(timer){
clearInterval(timer)
timer = null
}
timer = setInterval(() => {
callback.apply(this, args)
}, delay)
}
}
节流
函数在一段时间内只会执行一次,如果多次触发,那么忽略执行
/*
* @param {Function} callback 执行函数
* @param {Number} delay
*/
function throttle(callback, delay){
// 上一次执行时的时间
let start = Date.now()
return function(){
// 这一次触发时的时间
const now = Date.now()
// 如果相邻两次触发时间超过设置的delay, 说明已经过了限制时间,可以执行函数
if(now - start >= delay){
// 更新start, 方便下次对比
start = now
// 执行函数
return callback.apply(this, arguments)
}
}
}
6、修改this指向
| 方法 | 参数 | 返回值 | 作用 |
|---|---|---|---|
| call | thisArg, arg1, arg2, ...,注意,call接收的参数是一个参数列表 | 函数返回值 | 调用一个函数,将其 this 值设置为提供的值,并为其提供指定的参数。 |
| apply | thisArg, [arg1, arg2, ...],请注意这里,apply接收的参数是一个参数数组 | 函数返回值 | 调用一个函数,将其 this 值设置为提供的值,并以数组形式为其提供参数。 |
| bind | thisArg, arg1, arg2, ... | 新函数 | 创建一个新函数,当调用这个新函数时,它的 this 值被绑定到提供的值,并且它的参数序列前置指定的参数。 |
可以使用apply()和call()方法以新创建的对象为上下文执行构造函数
开始之前,因为这三个都是会改变this的指向,而this指向是js中很重要的一个部分,所以我们停一停,复习一下this指向,以便更好地认识和实现这仨兄贵
this指向
1、构造函数通过new关键字构造对象时, this指向被构造出的对象
function Game(name, type){
this.name = name;
this.type = type;
Game.prototype.showThis = function(){
console.log(this)
}
}
var dmc = new Game('DMC', 'ACT')
dmc.showThis()
可以看到指向的是被构造出来的dmc
2、全局作用域中,this指向window
3、谁调用方法,this指向谁
4、普通函数非通过其他人执行,this指向window
function fn(){
console.log(this)
}
fn() //window
function fn1(){
console.log(this) // window
function fn2(){
console.log(this)
}
fn2() // window
}
fn1()
// 被赋值后再调用
var obj = {
fn: function(){
console.log(this)
}
}
var fn3 = obj.fn
fn3() // window
5、数组里面的函数,按照数组索引取出运行时,this指向该数组,其实就是数组调用函数,所以指向数组
function fn(){
console.log(this)
}
var arr = [fn1]
arr[0]() // [fn] --> 就是arr
6、=>函数中的this指向他的父级非=>函数this
call、apply都不能改变=>函数的this
以上可以浓缩为:非
=>函数的this指向它执行时的上下文对象,=>函数的this指向它的父级非=>函数的this
7、call、apply、bind可以改变函数运行时候函数中this的指向
节流防抖中可以使用这种方式实现
call
call的主要用途:
- 直接调用函数
- 可改变函数内部的this
- 可以实现继承
使用上面的例子:
- fn() 相当于 fn.call(null)
- fn(fn2)相当于fn.call(null, fn2)
- obj.fn()相当于obj.fn.call(obj)
手写:
// context - 传入的执行上下文对象, 不传或者传null就默认为window
// args - 传入的参数列表
Function.prototype.myCall = function(context, ...args){
// this只有指向函数才能执行
if(typeof this != 'function'){
return new TypeError(`type error! ${this} is not a function`)
}
// 获取传入的执行上下文context, 若未传入则指向window, 就是例如fn.call()、fn.call(null)这种
context = context || globalThis // 使用globalThis是考虑到web和node下的全局this不一样
// 缓存this到传入的执行上下文对象去执行, 这一步相当于改变了this指向, 即指向了context
context.fn = this
// this指向改变后, 传入参数, 原fn在执行上下文对象运行并缓存此时fn在context下运行得到的返回值
const res = context.fn(...args)
// 用完就删除执行上下文中新增的this, 取消绑定
delete context.fn
// 返回原fn在context下运行得到的返回值
return res
}
apply
apply的用途:
- 调用函数,改变内部的this指向
- 参数必须为数组
- 主要应用于数组类
// apply和call很像,就后面传参有点改动
Function.prototype.myApply = function(context, args){
if(typeof this != 'function'){
return new TypeError(`type error! ${this} is not a function`)
}
context = context || globalThis
context.fn = this
// 因为传入的是数组, 所以如果没有参数就不要展开传进fn执行
const res = args ? context.fn(...args) : context.fn()
delete context.fn
return res
}
bind
- 改变原函数内部指向
- 返回改变指向后的拷贝
这里因为返回的是拷贝,所以我们要return的是一个function
Function.prototype.myBind = function(context, ...args1){
if(typeof context != 'undefined'){
return new TypeError(`type error! ${this} is not a function`)
}
context = context || globalThis
context.fn = this
return function(...args2){
const res = context.fn(...args1, ...args2)
delete context.fn
return res
}
}