我学要明白深层一点,这样可理解透,记得牢。
我学html,
1、标签 <body>,规范
2、构成文档的嵌套结构,一层层的 `
` 3、标签通过语义解析器分析,梳理出一颗Document Object Model树,像个目录一样
4、不同标签有不一样的渲染规范
我学css,
css分为:
A:找到html的DOM,所以有选择器,
行内样式直接挂在DOM节点上,权重最优先,
内部样式<style>div{}</style>和link的外部样式会按写的顺序合并在一块,对于作用在相同DOM节点上的同名冲突样式,就按优先级来决定生效。important最高》行内样式》id选择器》类选择器》标签选择器、伪类选择器》两个相同那么写在后面的覆盖前面的。(为什么行内高?因为都写到脸上了。为什么id高?因为都指名道姓了,为什么类选择器高?因为class本身就是用来css的,发现没,越专一的选择器类型越优先,泛泛的靠后)
B:跟Document Object Model树一样,css也会建立CSS Object Model树,原理就是各找各妈,根据选择器规则找到自己匹配的DOM节点并附身在它上面,所以一颗CSSOM树是基于DOM树的
C:通过js可以element.style.width = 12px直接修改CSSOM对象,但是这种是修改行内样式,也就是说样式表的样式因为优先级低所以内外部样式表的width都失效了。第二种是找到document.styleSheets列表修改样式表的样式,相当于修改<style>div{}</style>这种样式。
D:getComputedStyle()可以直接得到节点的最终优先级的样式。
基本上理解了DOM树和CSSOM树,对于背诵html标签和css样式属性有帮忙,因为标签会不断增加,样式也会增加,但是DOM的原理就这样,css选择器和样式生效原理就这样,高屋建瓴,学起旁支末节就容易记住。
我学js
一个超级棒的js学习网站:https://zh.javascript.info/
这么说,html和css是死的,js是活的,因为html 、css更像静态的布局,但是浏览器跑起来靠的是程序运行,js就是运行的程序。js需要跑在js引擎里。
JS包含两方面特点,第一是可以像后端语言那样执行逻辑运算,算一门后端语言。第二是,js能够调用浏览器中的API,因为js的ECMAScript 6规范的基本语言就是为JavaScript写的,定义了浏览器应该提供什么和js应该怎样。
所以,一是把js当成一门语言去学,看他的语法。
既然是语言,避免不了要有数据类型,有变量,有函数方法,有对象调用方法,有if、for循环等,这是基本的语法知识,不管什么语言,写法不一样,逻辑差不多。
二是把js当成跟html交互的API去学。
据说,DOM对象(CSSOM)是浏览器解析html封装出来的,然后传给js引擎里,这样JS就能够调用这些相关的API。那么nodeJS引擎不去解析html生成DOM对象,那么就没有。其实js已有对象包括:
不同的浏览器内核里面的渲染引擎各自的,我道听途说,渲染引擎复制页面的解析,最终布局和绘制。
js引擎跑js代码,js代码可以做运算,可以存储变量,可以通过DOM提供的API修改DOM节点结构,可以修改css样式。所以说js是动的,活的。
像nodeJS引擎目前可以运行js,但是它还不能像浏览器解析DOM,因为它是js引擎。
在非严格模式下(不适用"use strict"),可以把
<script>
js代码
</script>
当成一个大的js对象(假设是script对象), window、document都是这个大对象script里头的对象变量。
那么
function f(){
this // 这个函数直接在最外层,this = script环境
}
这个函数执行的时候,等于被大对象script调用,所以this指向的是整个js环境变量。
那么,如下,写在对象里的function,其实跟上面说的没什么两样:
let obj = {
doIt: function doIt() {
return this;//属于obj,所以this=obj
}
};
属性函数doIt等于被obj对象调用,所以this指向的是obj对象。
上面用script大变量作为最外层this指向,下面解析下为什么这么理解: 请看:
function a() {
console.log(this);
}
function b(a) {
return function () { //返回一个封装的内部function
a();
}
}
let f = b(a); //f是一个调用b 把a封装之后返回的内部function
f();//执行返回的function
可以看到,console.log(this)打印出的是所属的大环境对象script的引用!
也即是说,谁(对象)来调用这个function,function里的this就是指向谁(对象)。
那么下面把a当成obj对象的属性:
let obj = {
a() { //改动,把a当成obj对象的属性
console.log(this);
}
}
function b(a) {
return function () {
a();
}
}
let f = b(obj.a); //只能通过obj.a访问
f();
可以看到,f()是直接被调用,所以this还是大环境对象script。
这样就有一个问题,请看:
let obj = {
a() { //改动,把a当成obj对象的属性
console.log(this);
}
}
obj.a();
上面通过obj.a()调用,this本应该是obj本身!因为obj写a函数的时候,默认是认为this是obj自己的。
也就是说,上面通过function b(a)封装a函数的时候,没有考虑到会传obj.a的情况,没有考虑到a函数使用this时候默认是自己的所属对象,而不是被封装的b的大环境script。(PS:而且,在严格模式下,"use strict",this不是指向大环境script)
所以,在封装时可以使用Function.prototype.call()方法,所以改成:
let obj = {
a() { //改动,把a当成obj对象的属性
console.log(this);
}
}
function b(a) {
return function () {
a.call(this); //call
}
}
let f = b(obj.a); //只能通过obj.a访问
f.call(obj); //call
解析上面的,函数f其实就是那个return的内部function,直接执行f()就会没有调用者,没有this,所以写成f.call(obj),call方法意思是把obj绑定到当前函数中作为this然后执行。同理,内部function中执行 a() ,a()已经脱离了调用者obj了,那么在a()内部的this也是没有的,所以也要绑定。
如上,其实还有个函数bind()操作,
call方法实质上包括两步:把对象当成this 绑定bind到函数上,然后执行函数。
f.bind(obj)返回一个包装了f的函数,f.bind(obj,arg1,...)(arg1,...)直接调用这些函数,参数arg1,...可以放bind也可放函数参数入口。
继续:说说嵌套函数的this,和箭头函数的this
上面有个嵌套的函数,如下:
function b(a) {
return function () {
a.call(this); //call
}
}
内部return function给予外部调用。
外部b(a)执行之后实际得到的是return的function,b(a)()才是真正的执行这个return的function。
有两个点要说明:
第一个点,this是谁:
this是直接调用该function的对象。比如b(a)()前面并没有出现object.b(a)()的object调用者,所以这个return的function直接调用是没有this的,这就是为何要用call函数指定一个this调用者。
对于多层return嵌套函数,如下:
let obj = {
a :function a() {
//a this是谁?
return function b(){
//b this是谁?
return function c() {
//c this是谁?
}
}
}
}
如果obj.a(),那么a函数的this就是obj,
但是如果obj.a()()又进一层调用到b函数,我们要知道,显式的function嵌套是传递不了this进去的,所以b函数的this就空了,但是:
obj.b = obj.a()
obj.b()
把内部的b函数赋值给一个对象后,再通过这个对象去调用,那么b的this又有了,是直接调用者obj。
所以this指向的永远是直接调用(第一次就调用)当前函数的对象(ps:a.b.f(this是b对象))。
但是,第二个点: 箭头函数(=>)能够把外部的this传递进去,也就是内部this等于箭头函数外面的:(箭头函数块内的this、arguments都是来自环境上下文中的)
let obj = {
a :function a() {
//a this是谁?
console.log(this)
return () =>{
//b this是谁?
console.log(this)
return () => {
//c this是谁?
console.log(this)
}
}
}
}
obj.a()()()
如上,不管嵌套多少层,箭头函数内部的this始终等于外一层的this。
所以,在箭头函数够不着的地方,可以用that=this先把this存下来,但是要注意
继续,说说闭包
闭包可以理解为获得一个父作用域的内部的引用,可以直接访问内部引用,而内部引用又可以访问父作用域中的变量,从而效果上把父作用域反向地作为这个内部引用的“存储空间”。
function parent() {
let i = 0;
return function son() {
//可以访问 i
}
}
如上,通过let son = parent()来获得内部son的引用,这时候就体现了闭包,通过son引用可以调用son()函数,而son函数又可以访问到parent函数的变量,从而体现了parent为son服务。
parent()可以多次调用,每次调用都会初始化一个parent中的变量初始值,所以得到的每个son都有自己的独立环境,son执行过程中互不影响。
也可以写成:
let son =
(function parent() {
let i = 0;
return function son() {
//可以访问 i
}
})()
这样parent函数外部不可达,在定义的时候直接调用返回内部函数引用。
某些知识链接blog.poetries.top/
继续,说说原型链的数据结构
在JavaScript的世界里,第一个东西就是Object,然后可以实例化:let obj = new Object()。
那我问你,Object和obj的区别是什么?
Object就好比一个理论上的事物,而obj是根据理论创造出的实物对象。
obj = {name:"tom",age:12}这个事物可以有自己的属性和值。
问题就来了,那Object是不是一个实际的对象呢,它有没有自己的属性呢???就好比现实世界的理论公式,它要写在纸上,记在脑子里,理论需要载体记录。同理,Object是一个对象的抽象定义,它也要一个载体,这个记录它属性的载体对象就是[[prototype]]。如下
解析:Object不是一个实例化对象,但是在JavaScript中默认给这个Object函数创建了唯一一个对象,Object.prototype就指向这个对象,所以不用实例化,属于Object函数的所有属性和值都可以存到这个prototype对象中。
为什么说Object是一个函数,其实可以理解为Js里Object、Function、Array、Boolean等等都是构造函数,所谓实例化就是new 构造函数().
因为构造函数是抽象的规范,没法保存变化的属性值,所以每个构造函数内置一个[[prototype]]用来存属性值。所以Object.prototype就是一个原型对象。
继续说到原型链:
上面理解了原型,那么原型链就好理解了,就是后一个对象指向前一个对象的链,而且是对象的原型属性指向前一个对象:
obj.__proto__ -> Object.prototype //->是属性指向的意思
obj1.__proto__ -> obj
obj2.__proto__ -> obj1
obj3.__proto__ -> obj2
上面的,说明:
第一,obj.__proto__为什么是-> Object.prototype,因为Object是个构造函数,Object不是对象,所以指向是指向Object的原型对象。
第二:obj.__proto__是什么?__proto__是实例化对象的一个原型属性,专门用来指向原型链的前一个对象。
第三:obj.prototype是undefined,prototype是类如Object构造函数用来表示自己的,实例化对象用的是__proto__。
第四:Object.prototype.__proto__是Object构造函数自己的原型指向,它指向null对象,null对象是一切原型链的终点。
第五,顺藤摸瓜,如上面代码中,obj3.__proto__ = obj2,顺着原型属性__proto__可以一直沿着原型链上溯直到null。比如document.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__=null 。
一切可以new的都是构造函数,比如Object,Function
构造函数不是对象,所以内置
显式原型对象prototype来存储属性、值实例化对象没有显式原型prototype,它有自己的
隐式原型__proto__,指向原型链的前面一个对象当原型链前面一个对象是构造函数时,那么
__proto__是指向构造函数的显式原型prototype对象(记住,原型链上的所有点都是对象)原型链上游的对象的属性可以被所有下游的对象共享访问,但是下游对象一旦修改该属性,实际是修改自己的属性,不会影响上游对象的属性。
所以,从原型链中我们很好的去理解js中,从构造函数new出一个对象然后不断的生成子类对象,这样一整个数据结构是怎样的,如下
上图解析:
obj2的原型指向obj,所以obj2.__proto__ === obj
arr是Array构造函数new出来的,所以arr的原型指向Array(前面说了,构造函数不是对象,所以是指向Array.prototype)
同理,Array.prototype.__proto__指向的是Object。
同时,Array.prototype.constructor就是Array的构造函数,其实就是===Array,为true。
另外:请看函数:
function Func() {
} //等同于 let Func = new Function("形参名","函数体{}里的内容")
let funcObj = new Func();
console.log(funcObj.__proto__ === Func.prototype) //true
定义一个Func,实质上是new Function(),也就是说Function是定义的Func(对象)的原型,然后new Func()又将Func实例化,所以原型指向为:
funcObj.__proto__ = Func.prototype
Func.__proto__ = Function.prototype
PS:可以这么理解,Func是定义的对象,有prototype,跟Object也是定义的,只是Object的定义是js自带默认的。
好的,继续:原型链的作用
举例来说:Object构造函数上有hasOwnProperty方法,在它的原型链的后面所有对象都可以使用这个方法。
也就是说,任何属于原型链上的对象,当访问自己的属性或方法函数找不到时,会沿着原型链一直往上找,直到找到,或者到尽头null。