米娜桑,哦哈哟~
该篇章主要基于链接中的参考内容和代码测试得出的结论,面向具有一定基础的前端开发者。如有错误,请指出与包涵。
原型链的解释
- https://juejin.im/post/5aa78fe66fb9a028d2079ca4
白话翻译:原型链就相当于对象上的一个链条,通过隐性原型属性__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
- https://juejin.im/post/59bfe84351882531b730bac2
- https://www.cnblogs.com/goloving/p/9380076.html
- 可以通过 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 += `${i} `
}
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)