【JavaScript】要点知识的个人总结(1)
- 2020 年 4 月 6 日
- 筆記
米娜桑,哦哈哟~
该篇章主要基于链接中的参考内容和代码测试得出的结论,面向具有一定基础的前端开发者。如有错误,请指出与包涵。
原型链的解释
白话翻译:原型链就相当于对象上的一个链条,通过隐性原型属性__proto__ 将其他相关的属性绑定以达到引用的效果,其链条的终点就是 Object.prototype,这就是原型链。
class.prototype === obj.__proto__
Object.create()声明的对象
Object.create({name: 1}) 相当于在这个对象的链条上再增加一条链条__proto__,所以 Object.create(null) 的结果是没有任何隐藏属性非常纯净且可高度定制的 {} (一般用于一些开源项目),当然通过
let a = {} a.__proto__ = null
也可以获得纯净的对象,不过生成的性能一般,通过以下代码实现 Object.create()。
function create(obj) { let F = function () {} F.prototype = obj return new F() } function create(obj) { let a = {} a.__proto = obj return a }
Function.prototype.bind() 的特别之处
通过 Function.prototype.bind() 声明的函数不是 Function 的实例,故不存在class.prototype === obj.__proto__
;不过可以通过
Object.prototype.toString.call()
可以得知它依然是一个函数。
并与 Function.prototype 是一致的。
new
- new 的过程如下
创建有 Object 函数实例出来的空对象;
将该对象绑定到相应的构造函数上,其此为 this 的上下文,如果没有返回对象的话,就返回 this
function zxNew() { let fn = arguments[0] if (typeof fn !== 'function') { throw new TypeError('first param must be a function') } Array.prototype.shift.call(arguments) let obj = Object.create(null); obj.__proto__ = fn.prototype; let res = fn.apply(obj, arguments); return res instanceof Object ? res : obj }
原型链继承的方式
- https://juejin.im/post/5b654e88f265da0f4a4e914c
- https://github.com/mqyqingfeng/Blog/issues/16
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/extends
原型继承
function Parent () { this.name = 'temax' } Parent.prototype.fn = function () { console.log(233) } function Child () {} Child.prototype = new Parent() let child = new Child()
缺点:属性共享、不能传参
经典继承——使用 call 方法借用构造函数的方式
function Parent (name) { this.name = name } Parent.prototype.fn = function () { console.log(233) } function Child (name) { Parent.call(this, name) } let child = new Child('temax')
缺点:不能继承prototype上的属性
组合继承——即原型链继承和经典继承的结合
function Parent(name) { this.name = name } Parent.prototype.fn = function () { console.log(233) } function Child(name) { Parent.call(this, name) } Child.prototype = new Parent() Child.prototype.constructor = Child let child = new Child(1)
缺点:父类函数执行两次
组合继承的优化
function Parent(name) { this.name = name } function.prototype.fn = function () { console.log(233) } function Child(name) { Parent.call(this, name) } Child.prototype = Parent.prototype child.prototype.constructor = Child let child = new Child()
缺点:prototype 的保存地址其实就是父级的保存地址,也就是说如果改变 child.prototype 会直接影响到父级的 prototype,所以需要加一个__proto__进行隔离
寄生组合的方式——比较理想的组合方式
function Parent(name) { this.name = name } function.prototype.fn = function () { console.log(233) } function Child(name) { Parent.call(this, name) } Child.prototype = Object.create(Parent.prototype) child.prototype.constructor = Child
class的继承方式
class Parent{ } class Child extends Parent{ }
this的指向
- this永远指向最后调用的对象。
也就是说 a.b.c.fn(),那么 fn 里面的 this 的指向 c 的属性。
如果令 d = b.c.fn;则 a.d() 中的 this 是指向 a 的。 - setTimeout 的 function 中的 this 是指向 window 对象的。因为 setTimeout 实则为 window.setTimeout。
- 可通过定义this的变量来调用或使用 apply/call/bind 的方式改变 this 指向。
箭头函数的this
- 自身没有声明 this,所以会在作用域链上找最近的 this。
- 不可以当作构造函数。
- 不能用作 Generator 函数。
- 不能使用 arguments,arguments 可以用 …rest 取代。
apply、call、bind
- 可以通过 apply 和 call 对对象进行调用, 使得函数内的 this 绑定到该调用的值。
- apply 和 call 的区别就是传入方式的不一样,前者是数组传参的方式,后者是直接传参的方式。
- bind 跟 call 一致,只不过 bind 会创建并返回一个新的函数。
- 实现机制如下
call (apply同理,只是改变参数的形式)
Function.prototype.call = function(){ if (typeof this !== 'function') { throw new TypeError('Called object must be a function') } let obj = Object(arguments[0]) || window, obj.fn = this, result; arr = arguments.slice(); arr.shift(); result = obj.fn(...arr) delete obj.fn return result }
bind
如果要模仿 Function.prototype.bind() 得到的函数 fn,需要设置
fn.__proto__ = Object.prototype fn.prototype = undifned
Function.prototype.bind1 = function () { let fn = this; if(typeof fn === 'function'){ if(fn !== Function.prototype){ let obj = Object(arguments[0]) || window, args = Array.prototype.slice.call(arguments,1), TempFn = function () {}, FnBind = function () { //当使用new来构造函数(通过bind得到的)时,会忽略bind(obj)的obj //对于这个的理解,应该结合new和call的实现来解释,这个时候的this是指向 Object.create(null),如果没有设置prototype的话 //因为原生得到的(new fn1) instanceof fn1 为 true, // (new fn1) instanceof fn 也为 true, //又因为 TempFn.prototype = fn.prototype;(只要满足 ojb.__proto__ === class.prototype.constructor) //所以用改造后得到的 (new fn2) instanceof TempFn 为 true //简单来说就是,当为构造函数的时候,这里的this是等于指向实例,而实例 instanceof fn1 为true, 实例 instanceof fn也为true, //而只要满足 ojb.__proto__ === class.prototype //那么 instanceof 得到的就是 true,所以可以用 this instanceof TempFn 来表示 let bindArgs = Array.prototype.slice.call(arguments); return fn.apply(this instanceof TempFn ? this : obj, args.concat(bindArgs)) }; TempFn.prototype = fn.prototype; FnBind.prototype = new TempFn; return FnBind }else { fn.prototype = undefined fn.__proto__ = Object.prototype return fn } }else { throw TypeError('Called object must be a function') } }
事件循环
- https://segmentfault.com/a/1190000004322358#comment-area
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop#运行时概念
- https://juejin.im/post/59e85eebf265da430d571f89
- https://segmentfault.com/a/1190000011198232
事件循环是和消息队列共同实现JS同步异步运行的机制。消息队列先进先出,一个循环里,先从队列中取出宏任务并执行,如果存在异步任务,便会将其回调函数注册发放到消息队列当中;再执行微任务,直到相应的执行栈为空为止;执行完后启动新的循环;事件循环是js的运行机制。
事件循环还分宏任务和微任务
- 宏任务:整体代码;setTimeout;setInterval,宏任务消息队列
微任务:Promise; process.nextTick,微任务消息队列 - 通过以下代码测试
setTimeout(function(){ console.log('定时器开始啦') }); new Promise(function(resolve){ console.log('马上执行for循环啦'); for(var i = 0; i < 10000; i++){ i == 99 && resolve(); } }).then(function(){ console.log('执行then函数啦') }); console.log('代码执行结束');
节点操作
- 添加节点可以通过 appendChild
- 通过 innerText 对节点添加文本信息
- 也可以通过 innerHTML 对节点添加 html 文本
- 利用 document.createElement 添加对应元素。
- 通过 node.addEventListener(type,listener,capture) 添加事件。第三参数为是否捕获,默认false,即冒泡
- document 为最大的节点。
- 当然也可以通过 DocumentFragment 来减少 dom 操作
- 以下是 ul 元素添加 li 元素的例子
let ul = document.querySelector('.cul'); // let ul = document.querySelectorAll('.cul')[0]; let a = ''; function myFunction(e){console.log(e)} for(let i=0;i<5;i++){ a += `<li onclick='myFunction(${i})'>${i}</li>` } ul.innerHTML = a
let ul = document.querySelectorAll('.cul')[0]; for(let i=0;i<5;i++){ let a = document.createElement('li'); a.innerHTML = i a.addEventListener('click',function () { console.log(i) }) ul.appendChild(a) }
let ul = document.querySelectorAll('.cul')[0]; let fragment = document.createDocumentFragment() for(let i=0;i<5;i++){ let a = document.createElement('li'); a.innerHTML = i a.addEventListener('click',function () { console.log(i) }) fragment.appendChild(a) } ul.appendChild(fragment)