JavaScript
原型与原型链 prototype和__proto__
写在前面
我听过一种说法叫:原型prototype
和__proto__
是函数特有的。这句话倒也没错,但他举的例子有些问题。
function fn(){}
console.log(fn.prototype) // 此处可以正常输出,代表拥有prototype这个属性
const arr = []
console.log(arr.prototype) // 此处只会输出undefined,代表不拥有这个属性
以上是举例,确实没有任何错误,从代码上来说。
所以得出结论:数组和对象是没有原型的。
从证明过程上来说,我不认同这一点。但从结果上来说,我是认同的。
原型
现在,我开始解释为什么我觉得上面的例子有些问题。
先给一段常见的代码来展示一下
// 如果我想在项目中所有的数组变量中嵌入某个自定义方法
// 那我是不是该这么写,对象同理
Array.prototype.myfunc=function(){
console.log('myfunc')
}
// 定义对象或者数组 常见版
const obj = {}
const arr = []
// 陌生版 其实效果都一样
const obj = new Object()
const arr = new Array()
根据这段代码,很明显能知道:哦!原来我们常用的Array
和Object
全局变量是个函数啊。
那么再看上面的那个例子,仔细看是不是对比级别有些问题
function fn(){}
const f1 = new fn()
const obj = new Object()
console.log(f1.prototype) // undefined
console.log(obj.prototype) // undefined
console.log(fn.prototype) // 有对应原型的输出
console.log(Object.prototype) // 有对应原型的输出
所以要实例与实例对比,原型与原型对比。这才有可比性。悄咪咪说一句,其实String
和Number
也是一样的。
原型的补充 关于Function
后面,我也尝试了使用Function
去定义函数,还挺好玩的
const func = new Function ([arg1, arg2, ...argN], functionBody);
const sum = new Function('a', 'b', 'return a + b')
const hello = new Function('console.log("hello world")')
再换个角度一想,new Function
出来的何尝也不是实例,虽然一般也见不着这种,但他确实也有prototype
。
可能其他数据类型单独把原型抽出来的原因是怕开发人员通过变量直接对数据类型的自带方法或属性做修改吧。
原型链
function fn(){
console.log('我被执行啦')
}
const func = new fn()
console.log(func.num) // undefined
fn.prototype.num = 1
console.log(func.num) // 1
func.num = 2
console.log(func.num) // 2
从上面代码可以看得出来,func中本来没有num属性;但通过对原型上添加属性的方式,func就有了num属性。而对func直接添加同名属性时,num变成了func直接添加进去的属性值。
那么这就是原型链的两个特征
- 就近原则 当访问某个变量下的属性时,会优先使用离他最近的同名属性
- 传递原则 当这一层没有该属性时,会顺着原型链
__proto__
一层一层找下去,直到null
原型链的最底层
判断原型的方式
Object.prototype.toString.call()
可以判断所有的数据类型,会返回一个字符串。
> Object.prototype.toString.call([])
'[object Array]'
> Object.prototype.toString.call({})
'[object Object]'
> Object.prototype.toString.call(function (){})
'[object Function]'
> Object.prototype.toString.call(1)
'[object Number]'
> Object.prototype.toString.call('1')
'[object String]'
> Object.prototype.toString.call(Symbol())
'[object Symbol]'
> Object.prototype.toString.call(null)
'[object Null]'
> Object.prototype.toString.call(undefined)
'[object Undefined]'
> Object.prototype.toString.call(true)
'[object Boolean]'
instanceof
用来判断某个变量的原型链是否存在某个原型(注意整条原型链的原型)
> [] instanceof Object
true
> [] instanceof Array
true
typeof
一般只用来判断基本数据类型
> typeof []
'object'
> typeof {}
'object'
> typeof function(){}
'function'
> typeof null
'object'
> typeof Symbol()
'symbol'
> typeof 1
'number'
> typeof '1'
'string'
> typeof undefined
'undefined'
> typeof true
'boolean'
可以看得出来,它并不能很好地判断引用数据类型
闭包
防抖与节流
防抖:某个函数在规定时间内,只使用最后一次触发的参数执行;简单来说:将多次操作只保留最后一次。
function debounce(func, wait, immediate) {
let timeout
return function () {
const context = this
const args = [...arguments]
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(() => {
timeout = null
!immediate && func.apply(context, args)
}, wait)
immediate && func.apply(context, args)
};
}
使用场景:输入框联想功能调用接口、多频率点击事件等
节流:某个函数在规定时间内,只调用第一次
function throttle(fn, wait) {
let pre = 0;
return function (...args) {
let now = Date.now();
if (now - pre >= wait) {
fn.apply(this, args);
pre = now;
}
};
}
使用场景:轮播图左右按钮、提交表单(防抖也可以,不过一般建议用loading覆盖操作层)
PS:两种皆为闭包使用场景
实现once函数 传入函数只执行一次
function once(func) {
let flag = true;
return function () {
if(flag){
flag = false
func.apply(null,arguments)
}
}
}
JavaScript延迟加载的方式
JavaScript 会阻塞 DOM 的解析,因此也就会阻塞 DOM 的加载。所以有时候我们希望延迟 JS 的加载来提高页面的加载速度。
- script 标签的defer属性 脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用。
- script 标签的Async属性 是在外部 JS 加载完成后,浏览器空闲时,Load 事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行,该属性对于内联脚本无作用。
- 动态创建script标签,监听dom加载完毕再引入js文件
图片的懒加载和预加载
- 预加载 提前加载图片,当用户需要查看时可直接从本地缓存中渲染
- 懒加载 减少请求或延迟请求
实现休眠sleep函数
据说有人以前用这个方法来一钱多赚,美其名曰提升性能?(笑)
promise
function sleep(ms) {
const pause = new Promise((resolve)=>{
setTimeout(resolve,ms)
})
return pause
}
sleep(1000).then()
promise + async
function sleep(ms) {
const pause = new Promise((resolve)=>{
setTimeout(resolve,ms)
})
return pause
}
async function testSleep() {
await sleep(1000)
}
new 操作符
- 创建一个新对象,即
{}
- 让这个对象的
[[Prototype]]
(_proto_
)属性指向构造函数的prototype
- 构造函数内部的
this
被绑定到这个对象 - 如果构造函数返回值是一个对象,那么就会返回这个对象。否则返回创建的对象。
// fn构造函数
function MyNew(fn){
// 判断fn是否为函数
if(typeof fn !== "function")
throw Error(fn+"is not a function");
// 生成一个新的对象
let newObj = {};
// 让这个对象的`[[Prototype]]`属性指向构造函数的`prototype`
newObj.__proto__ = fn.prototype;
// 以上两步可以用这个代替
// const newObj = Object.create(fn.prototype);
// 获取传入构造参数
let arg = Array.prototype.slice.call(arguments,1);
// 绑定this,并执行构造函数
let res = fn.apply(newObj,arg);
// 判断返回值是否是对象
return res instanceof Object?res:newObj;
}
JavaScript中this的五种情况
- 作为普通函数执行时,
this
指向window
。 - 当函数作为对象的方法被调用时,
this
就会指向该对象。 - 使用
new
操作符调用,会返回新的对象或方法本身具有返回引用数据类型的变量 - 箭头函数,
this
指向取决于定义时的上级作用域的最近一级的对象。 apply、call和bind
会修改方法执行时的this
指向,区别在于bind
并不会立即执行,只是返回一个函数。而其他两个都会立刻执行。
跨域方案
CORS:服务端设置Access-Control-Allow-Origin
即可,前端无须设置,若要带cookie
请求,前后端都需要设置。
代理跨域:启一个代理服务器,实现数据的转发
JS隐式转换
一文让你搞懂JavaScript隐式转换 - 掘金 (juejin.cn)
普通函数和箭头函数的区别
1、普通函数的this与调用对象相关,而箭头函数的this指向由定义时的上一级确认,且无法改变 2、普通函数有arguments,而箭头函数没有 3、普通函数可以用new调用,箭头函数不可以使用new关键字 4、普通函数有原型链,而箭头函数没有
JS垃圾回收机制
一文让你彻底搞懂JS垃圾回收机制 - 掘金 (juejin.cn)
以下为埋坑问题,目前懒得写,又怕以后忘记,所以先写个标题
- 重绘与回流 也许该写个CSS相关的了
- EventLoop 事件循环 浏览器环境与node环境 (好像在ES相关引用了一篇其他人的文章)不想写了
- setTimeout、Promise、Async/Await的区别
- 事件捕获、冒泡和委托
- 深拷贝
- js延迟加载
- call、apply 、bind