HTML\CSS\JS三大件学习心得记录

220 阅读12分钟

我学要明白深层一点,这样可理解透,记得牢。

我学html,

1、标签 <body>,规范

2、构成文档的嵌套结构,一层层的 `

` 3、标签通过语义解析器分析,梳理出一颗Document Object Model树,像个目录一样

4、不同标签有不一样的渲染规范

image.png

image.png

我学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树的

image.png

C:通过js可以element.style.width = 12px直接修改CSSOM对象,但是这种是修改行内样式,也就是说样式表的样式因为优先级低所以内外部样式表的width都失效了。第二种是找到document.styleSheets列表修改样式表的样式,相当于修改<style>div{}</style>这种样式。

image.png

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的functionb(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存下来,但是要注意


继续,说说闭包

zh.javascript.info/closure

闭包可以理解为获得一个父作用域的内部的引用,可以直接访问内部引用,而内部引用又可以访问父作用域中的变量,从而效果上把父作用域反向地作为这个内部引用的“存储空间”。

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]]。如下

image.png

解析: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出一个对象然后不断的生成子类对象,这样一整个数据结构是怎样的,如下

image.png

上图解析:

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。