通过HTML和CSS的学习,我们可以做到实现静态的页面。然而实现动态页面或者实现交互功能就离不开JavaScript。所以在这里对半个月学习js做一个总结。
JavaScript(简称“JS”) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名的,但是它也被用到了很多非浏览器环境中,JavaScript 基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式和声明式(如函数式编程)风格。
页面内嵌
为符合 web 标准(w3c 标准中的一项)结构(html)、行为(js)、样式(css)
相分离,通常会采用外部引入。
一个文件中可以包括多个 css,js——不混用
特殊写页面,大部分写在外部——不混用
如果同时写了内部的 js 和外部的 js,那么是外部的 js 文件显示出来
3.2.1 变量(variable)
HTML,css 不是编程语言,是计算机语言,编程语言需要有变量和函数
变量是存放东西,方便后续使用的框。
var a = 10; var b = 10; var c = 10; var d = 10; var e = 10;
3.2.2 命名规则(用接近的英文单词)
3.2.3 值类型
3.2.4 js语句基本规则
语句后面要用分号结束“;”但 function test(){},for(){},if(){}后面都不用加分号
js 语法错误会引发后续代码终止,但不会影响其它 js 代码块
错误分为两种:
1)低级错误(语法解析错误),不能写中文
2)逻辑错误(标准错误,情有可原,错的那个执行不了)
书写格式要规范,“= + / -”两边都应该有空格
3.3.1 运算操作符
3.3.2 比较运算符
“>”,”<”,”==”,“>=”,“<=”,”!=”比较结果为 boolean 值。但凡是运算符,都是要有运算的
用到布尔值,true 或 false[字符串的比较,比的是 ASCII 码(七位二进制 0000000) >, <]
= =,等不等于(NaN 不等于任何东西,包括他自己,NaN 得不出数,又是数字类型,就是 NaN)
->=,<=,!=是否不等于,非等。比较结果为 boolean 值:true 和 false
3.3.3 逻辑运算符
“&&”,“||”,“!“运算结果为真实的值
被认定为 false 的值:转换为布尔值会被认定为 false 的值 undefined,null,NaN,
“”(空串), 0, false
3.4.1 数组
所有的JavaScript变量都是对象。数组元素是对象。函数是对象。因此,你可以在数组中有不同的变量类型。你可以在一个数组中包含对象元素、函数、数组。
3.4.2 对象 object
var obj = {
里面存属性和方法
key 属性名:value 属性值,
}
在{}面用。属性与属性之间用逗号隔开
//属性值可以双引号或单引号;属性名是为了方便找到他,只是一个辅助。
3.5.1typeof
typeof 能返回的六种数据类型(区分数字类型)
number、string、boolean、undefined、object、function
(null 返回 object,最早是代替空对象的)
如果定量没定义就直接访问,就 a is not defined 报错;有一种特殊情况,当且仅当把未定义的变量放到 console.log(typeof(a));里面就访问,不报错,返回 undefined
3.5.2 显示类型转换
Number(mix) 是想把里面的东西转换成数字
var demo = undefined;
var num = Number(demo);
console.log(typeof(num) + “:” + num);
答案显示 Number:NaN
(可以把null、空字符串、false转换为0;把true转换为1)
parseInt(string,radix)parse 是转化,Int 是整型,整数,目的是把里面转换成整数。parseInt 从数字类开始看,看到非数字类为止,返回原来的数。
(把空字符串、true、false、null转换为NaN)
var num = parseInt(demo ,radix);
radix 写成 16,系统会认为是以 16 进制为基底,10(一零)是 16 进制的一零,是
以 16 进制为基底,把他转成为 10 进制的数字(就是 16),上面是以目标进制为基底,
转换成十进制(radix 范围是 2-36)
parseFloat(string):转换成浮点数字,就是正常小数。parseFloat 从数字类开始看,看到除了第一个点以外的非数字类为截止,返回前面的数。
toString
- 想把谁转换成字符串,就写成谁.toString
- undefined 和 null 不能用 toString
- toString(8);这里的 radix 意思是以十进制为基底,转换成目标进制(即 8 进制)
String(mix):String(mix)转换成字符串,写什么都成了字符串
Boolean():转换成布尔值 false 和 true
3.5.3 隐式类型转换
隐式类型转换是跟你转换了也不知道
隐式类型转换内部隐式调用的是显示的方法
隐式类型转换包括 isNaN () ,++,--, +/-(一元正负),+,*,% ,&&,|| ,!,<,>,<= ,>= ,== ,!=
isNaN ():当你把一个数放到()里,它能判断是不是 NaN,先把括号里面的放到 number里面转换,然后返回来
++/-- (加加减减) +/-(一元正负)
(尽管转换不成数字,也会转换成数字类型,因为里面隐式的调用了一个 number)
“+”:+隐式类型会转换成 string,当加号两侧有一个是字符串,就用调用 string,把两个都变成字符串
" * ,%" 乘和模都会转换成 number.
&& || !与或非,都是有类型转换的,不过是返回的是表达式的值,不是隐士类型转换的值,但是判断是一个类型转换的值
< > <= >=
有数字相比较的,就会隐士类型转换成数字类型
两个字符相比,比较ASCⅡ
== !=
undefined == null //答案 true
NaN ==NaN //答案 false,NaN 是唯一一个连自己都不等于的
3.5.4 不发生类型转换
function 随便起个名(){};
编程讲究高内聚,弱偶合。
3.6.1 定义
函数声明
a.定义一个函数可以先写一个 function,函数就是另一个类型的变量。
b.function 随便起个名(){函数体};
c.函数名起名:开发规范要求,函数名和变量名如果由多个单词拼接,必须符合小驼峰原则(第一个单词首字母小写,后面的首字母大写)
d.c 语言和 c++,他们打印指针,会输出指针的地址,而 js 这种弱数据语言(解释性语言)永远不输出地址,输出地址指向房间
函数表达式
a.命名函数表达式
b.匿名函数表达式
3.6.2 组成形式
函数名称
function test(){}其中 function 是函数关键字,test 是函数名,必须有(){},参数可有可没有,参数是写在()括号里面的。如果写成 function test(a,b){},相当于隐式的在函数里面 var a,var b 申明了两个变量,()括号里面不能直接写 var
参数(可有可没有,但是高级编程必有)
a.形参(形式参数):指的是 function sum(a,b){}括号里面的 a 和 b
b.实参(实际参数):指的是 sum(1,2);里面的 1,2
(天生不定参,形参可以比实参多,实参也可以比形参多。js 参数不限制位置,天生不定参数在每一个函数里面都有一个隐式的东西 arguments 这个是实参列表)
在 if 里面定义函数声明 function 是不允许的
返回值 return
a.结束条件和返回值 return,return 有终止函数的功能
b.没写 return,实际上是加上了一个隐式的 return
c.return 最常用的是返回值。本意是把一个值返回到函数以外
(任何全局变量都是 window 上的属性没有声明就是赋值了,归 window 所有,就是在 GO 里面预编译)
(想执行全局,先生成 GO,在执行 test 的前一刻生成 AO在几层嵌套关系,近的优先,从近的到远的,有 AO 就看 AO,AO 没有才看 GO)
3.8.1 什么是闭包
当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄露。
内存泄漏就是内存占用,内存被占用的越多,内存就变得越来越少了,就像内存被泄露了一样
(但凡是内部的函数被保存到外部,一定生成闭包)
3.8.2 闭包的作用
3.9.1 定义
此类函数没有声明,在一次执行过后即释放(被销毁)。适合做初始化工作。针对初始化功能的函数:只想让它执行一次的函数立即执行的函数也有参数,也有返回值,有预编译。(写成(function abc(){}())也调用不到)
3.9.2 立即执行函数的两种写法
3.10.1 创建方法
双引号和单引号都是表示的字符串,写双引号也可以写单引号,但是为了跟后端 php配合最好写单引号。如果要打印一个单个的引号,用正则表达式转义字符\注意等号和冒号的用法 obj.say = function(){} var obj = { name : ‘abc’}
3.10.3 包装类
只有原始值数字是原始值,原始值不能有属性和方法,但经过了包装类(加隐式)可以调用一些属性与方法
undefined 和 null 不可以有属性
var num = 4;
num.len = 3;
//系统隐式的加上 new Number(4).len = 3; 然后 delete
console.log(num.len);
//系统隐式的加上 new Number(4).len; 但是这个 new number 和上面的 new number
不是同一个,所以返回 undefined
而上面这些隐式的过程就是包装类。原始值要赋属性
值需要调用包装类,赋了跟没赋值是一样的。
3.10.4 原型
prototype是构造函数的属性,__ proto__是对象的属性
原型是隐式的内部属性,自己加是没有用的
3.10.5 原型链
原型链上属性的增删改查
原型链上的增删改查和原型基本上是一致的。只有本人有的权限,子孙是没有的。
谁调用的方法内部 this 就是谁-原型案例
绝大多数对象的最终都会继承自 Object.prototype
Object.create(原型);
Object.create()在括号里面只能放 null 或者 Object,其余会报错
原型方法上的重写
原型上有这个方法,我自己又写了一个和原型上同一名字,但不同功能的方法,叫做重写(同一名字的函数,不同重写方式)
数字想用 toString 方法,要经过包装类包装 new Number(num)然后. toString
3.10.6 call/apply
作用,改变 this 指向。
区别,后面传的参数形式不同。
(toFixed 是保留两位有效数字;Math.random()是产生一个 0 到 1 区间的开区间 随机数;向上取整 Math.ceil();向下取整 Math.floor())
call
任何一个方法都可以.call
Person.call( );括号里面可以传东西
如果 Person.call( obj );里面的 call 让 person 所有的 this 都变成 obj
不 new 的话,this 默认指向 window。call 的使用必须要 new
call 的第一位参数用于改变 this 指向,第二位实参(对应第一个形参)及以后的参数都当做正常的实参,传到形参里面去借用别人的方法,实现自己的功能。
(只能在你的需求完全涵盖别人的时候才能使用)
apply
apply 也是改变 this 指向的,只是传参列表不同,第一位也是改变 this 指向的人,第
二位,apply 只能传一个实参,而且必须传数组 argunments
call 需要把实参按照形参的个数传进去
3.10.7 继承发展史
1.传统形式 ==> 原型链 //问题:过多的继承了没用的属性
2.借用构造函数 ==>利用 call、apply 所以不算标准的继承模式
3.共享原型(较好的继承方法)//问题:不能随便改动自己的原型
function inherit(Target,Origin){
Target.prototype = Origin.prototype;
}
//问题:更改一个的原型会影响到另一个
4.圣杯模式
圣杯模式是在方法三的共有原型,但是在共有原型的基础上有改变。
共享原型是:son.prototype=father.prototype
圣杯模式是:另外加个构造函数 function F(){}当做中间层,然后让 F 和 father 共
有一个原型 F.prototype=father.prototype,然后 son.prototype = new F();使用原型链形成了继承关系,现在改 son.prototype 就不会影响 father.prototype
联系到闭包作用:可以实现封装,属性私有化。
3.10.8 命名空间(其实就是对象)
管理变量,防止污染全局,适用于模块化开发。
下面是现在公司最常见的方法:用闭包来解决(也可用 webpack),返回方法的调用。
init 是初始化,入口函数,入口名字。init 调用了这个函数的功能
3.10.9 实现链式调用模式
用 return this,就可以连续调用和执行了
3.10.10 属性的表示方法(查看属性)
obj.prop 查看就用.XXXX obj[“prop”] 中括号也是访问属性的方法
用方括号来访问属性也是一样的(里面必须是字符串)
这两种基本上完全相同 obj.name → obj [ ‘name’ ]
想实现属性名的拼接,只能用方括号的形式
3.10.11对象的枚举enumeration
for in 循环(简化版 for 循环),目的是遍历对象,通过对象属性的个数来控制循环圈数,这个对象有多少属性循环多少圈,而且在每一圈时,都把对象的属性名放到 Prop里面 在枚举里面,一定要写成 obj[prop]不能加字符串
上面就是 for in 循环,就是遍历用的。通过对象的属性个数来控制循环圈数,有多少个属性就会循环多少圈。
for(var prop in obj)在循环每一圈的时候,他会把对象的属性名放在 prop 里面。想遍历谁就 in 谁,prop 可以写别的,obj 就是我们想要遍历的对象。var XX in XX 的格式是固定的。
obj.prop 系统以后我们写的是 obj[‘prop’],系统会以为我们是在让他访问 prop这个属性,不会把 prop 当成一个变量来使用。写成 obj[prop]就可以成功访问了
逗号操作符,会看一眼 1,在看一眼 2,然后返回第二个
克隆
把上面做一个兼容性的写法,为了防止用户不传 target(容错),给了参数就直接用,不给就当空对象
function clone(origin,target){
var target = target || {};
for(var prop in origin){
target[prop] = origin[prop];
}
return target;
}
现在我们想实现深度克隆(只考虑数组和对象),copy 过去后,我改,但是你不会改,引用值不能直接拷贝
function deepClone(origin,target){
var target = target ||{},
var toStr = Object.prototype.toString,
var arrStr = "[object Array]";
for(var prop in origin){
if (origin.hasOwnproperty(prop)){
if(origin[prop]!== "null" && typeof(origin[prop])=='object'){
target[prop] = [];
}else{
target[prop] = {};
}
deepClone(origin[prop],target[prop])
}else{
target[prop] = origin[prop];
}
}
return target;
}
深度克隆的步骤
1、先把所有的值都遍历一遍(看是引用值和原始值)
用 for ( var prop in obj ),对象和数组都可以使用
2、判断是原始值,还是引用值?用 typeof 判断是不是 object
1)如果是原始值就直接拷贝
2)如果是引用值,判断是数组还是对象
3、判断是数组还是对象?(方法 instanceof【看 a 的原型链上有没有 b 的原型】、
toString、constructor,建议用 toString,另外两个有个小 bug——跨父子域不行)
1)如果是数组,就新建一个空数组;
2)如果是对象,就新建一个空对象。
4、建立了数组以后,如果是挨个看原始对象里面是什么,都是原始值就可以直接考
过来了;或者,建立了对象以后,挨个判断对象里面的每一个值,看是原始值还是
引用值
5、递归
3.10.12this
3.10.1定义数组的方式
1)new Array(length/content);
2)字面量 var arr = [1,2,3,4,5];
var arr = [ ] ;数组自变量;var arr = new Array ( ) ;系统提供。两者区别就只一位数的情况。Array ( )括号里面只有一位数,就代表着长度,并且里面每一位都没有值。js 数组是基于对象的,数组是一种特殊的对象。
3.10.2数组常用的方法
var arr = [1,2,3,4,5,6];
arr.sort(function(){
return Math.random() - 0.5;
});
8.concat 连接, 把后面的数组拼到前面,并成立一个新的数组,不影响之前的两个数组。不能改变原数组。最后有三个数组。
9.toString 是把数组当做字符串展示出来
10.slice 从该位开始截取,截取到该位,并不改变原数组,这里也可以写负数。slice 并不改变原数组 slice 完了以后需要有东西接收,不然没有意义。slice 里面可以填 0 个参数,也可以填 1 个参数,也可以填两个参数
a.如果填两个参数,slice(从该位开始截取,截取到该位)
如 arr.slice(1,2)从第一位开始截取,截取到第二位
b.如果填一个参数,从第几位开始截取,一直截取到最后。
如果 arr.slice(1) ,从第 1 位开始截取,截取到最后一位.
c.不写参数就是整个截取数组(把类数组转换成数组的时候经常使用到)
11.join 括号里面需要用字符串形式(标准语法规定),就会用加的东西连接起数组。join 里面不传参默认用逗号连接。
join 可逆的东西:split( )是 string 字符串方法
split 按照什么拆分为数组。用什么拆,什么就没了,按-拆就去掉-,按 4 拆就去掉 4。
split 可以返回数组,数组可以返回字符串
3.10.3类数组
类数组:属性要为索引(数字)属性,必须要有 length 属性,最好加上 push 方法。
3.11.1try…catch
try 花括号{里面会正常执行,但是遇到 b 报错时 b 就执行不出来,后面的代码 c 就不执行了,但是外面的代码 d 还能执行}catch(e){ },这个 e 可以随便写,写 abc 都可以,也是个形参
3.11.2Error.name 的六种值对应的信息:
3.11.3es5.0 严格模式
浏览器是基于 es3.0 和 es5.0 的新增方法使用的。
如果两种发生了冲突,就用 es3.0。
es5.0 严格模式是指 es3.0 和 es5.0 产生冲突发部分就是用 es5.0,否则就用 es3.0。
1.DOM — > Document Object Model(文档对象模型)
2.DOM 定义了表示和修改文档所需的方法(对象、这些对象的行为和属性以及这些对象之间的关系。)DOM 对象即为宿主对象,由浏览器厂商定义,用来操作 html和 xml 功能的一类对象的集合。也有人称 DOM 是对 HTML 以及 XML 的标准编程接口
dom 不能改变 css 样式表,可以间接改变行间样式的 css
document 代表整个文档(如果给 html 标签上面再套一层标签就是 document)
4.2.1对节点的增删改查
注意哪怕整个文档只有一个 demo,也要加[0],不然选出来的就是一个组
querySelectorAll()和.querySelector()选出来的元素不是实时的(是静态的),所以一般不用,其他的再怎么修改,跟原来的没有关系
4.3.1遍历节点树
4.3.2节点的类型
元素节点 —— 1
属性节点 —— 2 (基本没用,)
文本节点 —— 3
注释节点 —— 8
document —— 9
DocumentFragment —— 11
后面的数字是调用 nodeType 返回的数字
规范化
4.3.3基于元素节点树的遍历(不含文本节点)
除 children 外,parentElement、node.childElementCount、firstElementChild、lastElementChild、nextElementSibling、 previousElementSibling 在 ie9 及以下不兼容。真正常用的就是 children,兼容性好
4.3.4 每一个节点的四个属性
Node.hasChildNodes();——他有没有子节点,返回值是 true 或 false
4.3.5 DOM接口
dom 结构树代表的是一系列继承关系
document 继承自 HTMLDocument.prototype。HTMLDocument.prototype 继承自 Document.prototype
DOM 结构树中,Node 也是构造函数,comment 是注释HTMLDocument 和 HTMLElement 实际上并列了对应的XML,但是因为不用了 XML就省略了。HTMLHeadElement 就是 head 标签,其余类推。
4.4.1常用操作注意
4.4.2 增
document.createElement(); //增加或创建元素节点(标签)——常见
document.createTextNode(); //创建文本节点
document.createComment(); //创建注释节点
document.createDocumentFragment(); //创建文档碎片节点
4.4.3 插——剪切操作
PARENTNODE.appendChild(); 可以理解成.push
PARENTNODE.insertBefore(a, b);一定是 div 先 insert a,before b(剪切效果)
4.4.4 删
parent.removeChild(); //返回移除子元素,就是被剪切出来了
child.remove(); //自尽,完全删除
4.4.5 替换
parent.replaceChild(new, origin); //用新的 new 去置换旧的 origin
4.4.6Element 节点的一些属性和方法
属性:
innerHTML ==> 可取,可写,可赋值
innerText ==> 可取,可赋值 (老版本火狐不兼容) / textContent(火狐使用这个,老版本 IE 不好使)
方法:
ele.setAttribute(“属性”,“值”) //设置
ele.getAttribute(“属性”); //取这个值
就是一种对象,是系统提供好的
var date = new Date()大写的 Date 是系统提供的一个构造函数,通过 new Date 的方法会给我们返回一个对象,这个对象就是一个日期对象。日期对象有很多属性和方法。小的 date 代表此时此刻的时间。用小的 date 调用方法,如 date.getDate( )
4.6.1Date对象方法
Date() 返回当日的日期和时间。
getDate() 制造出对象,从 Date 对象返回一个月中的某一天 (1 ~ 31)。
getDay() 今天是一周的第几天,如果是 2 是星期二,但是是指三天(第一天是周日,也就是 0)。从 Date 对象返回一周中的某一天 (0 ~ 6)。
getMonth() 一月份返回值是 0,从 Date 对象返回月份 (0 ~ 11)。
getFullYear() 从 Date 对象以四位数字返回年份。
getYear() 已废弃。请使用 getFullYear() 方法代替。
getHours() 返回 Date 对象的小时 (0 ~ 23)。
getMinutes() 返回 Date 对象的分钟 (0 ~ 59)。
getSeconds() 返回 Date 对象的秒数 (0 ~ 59)。
getMilliseconds() 返回 Date 对象的毫秒(0 ~ 999)。
getTime() 最有用 返回 1970 年 1 月 1 日(纪元时刻)至今的毫秒数。经常用于项目的计算时间。获取时间戳
setDate() 设置 Date 对象中月的某一天 (1 ~ 31)。
setMonth() 设置 Date 对象中月份 (0 ~ 11)。
setFullYear() 设置 Date 对象中的年份(四位数字)
setYear() 请使用 setFullYear() 方法代替。
setHours() 设置 Date 对象中的小时 (0 ~ 23)。
setMinutes() 设置 Date 对象中的分钟 (0 ~ 59)。
setSeconds() 设置 Date 对象中的秒钟 (0 ~ 59)。
setMilliseconds() 设置 Date 对象中的毫秒 (0 ~ 999)。
setTime() 以毫秒设置 Date 对象。机械之间交换时间
toSource() 返回该对象的源代码。
toString() 把 Date 对象转换为字符串。
toTimeString() 把 Date 对象的时间部分转换为字符串。
toDateString() 把 Date 对象的日期部分转换为字符串。
在控制台调用 date.getSeconds 就是 date 创建时间的毫秒数,是静止的,不是动态的。
这个 date 对象记录的是出生的那一刻的时间,不是实时的
4.6.2js定时器
setInterval(); //注意:setInterval(“func()”,1000);定时循环器
如果先定义 1000 毫秒,在后面改成 2000 毫秒,程序仍按 1000 毫秒执行,因为他只识别一次,不能通过改变 time 改变 setInterval 的快慢。
setInterval 计算时间非常不准
注意:setInterval();是 window 的方法,在全局上就算不写 window. setInterval();他也会上全局的 GO 里面查找,所以不写 window.也行。
每一个 setInterval();都会返回一个数字,作为唯一的标识,有唯一标识就可以把他清除掉(利用 clearInerval 清除)
clearInterval(); //能让 setInterval 停止
一般写了 setInterval 就要写 clearInterval
setTimeout(); //正真的定时器,隔了一段时间后再执行(起推迟作用),并且只执行一次。返回一个数字做唯一标识。标识不会重叠。
clearTimeout(); //清除 setTimeout();让他停止执行
这种写法,setTimeout();还没执行就被清除了,就执行不了
setInterval();setTimeout();clearInterval();clearTimeout();这四个都是全局对象,都是window 上的方法,内部函数 this 指向 window
4.7.1 查看滚动条的滚动距离
window.pageXOffset 横向/pageYOffset 纵向滚动条
IE8 及 IE8 以下不兼容(IE9 部分兼容)IE9 以上能用
IE8 及 IE8 以下的使用方法
1)document.body.scrollLeft/scrollTop——求横向距离和纵向距离
2)document. documentElement.scrollLeft/scrollTop
上面两个兼容性比较混乱,其中一个有值,另外一个的值一定是 0。这两个最好的用法是取两个值相加,因为不可能存在两个同时有值
如 document.body.scrollLeft + document. documentElement.scrollLeft
封装兼容性方法(哪个浏览器都好用),求滚动轮滚动距离 getScrollOffset()——求滚动条的位置
function getScrollOffset(){
if(window.pageXOffset){
return{
x : window.pageXOffset,
y : window.pageYOffset
}
}else{
return{
x : document.body.scrollLeft + document.documentElement.scrollLeft,
y : document.body.scrollTop + document.documentElement.scrollTop
}
}
}
4.7.2 查看视口的尺寸
可视区窗口就是编写的 html 文档可以看到的部分,不含菜单栏、地址栏、控制台
document.compatMode 是用于判断是怪异模式还是标准模式的
"CSS1Compat"标准模式 ''BackCompat"怪异模式向后兼容
function getViewportOffset(){
if(window.innerWidth){
return{
w : window.innerWidth,
h : window.innerHeight
}
}else{
if(document.compatMode ==="BackCompat"){
return {
w : window.innerWidth,
h : window.innerHeight
}
}else{
return {
w : document.documentElement.clientWidth,
h : document.documentElement.clientHeight
}
}
}
}
}
4.7.3 查看元素的几何尺寸
4.7.4 查看元素的尺寸
dom.offsetWidth,dom.offsetHeight
求得值是包括 padding 的
4.7.5 查看元素位置
dom.offsetLeft, dom.offsetTop
对于无定位父级的元素,返回相对文档的坐标。
对于有定位父级的元素,返回相对于最近的有定位的父级的坐标。(无论是 left 还是margin-left 等都是距离。 )
dom.offsetParent
返回最近的有定位的父级,如无,返回 body, body.offsetParent 返回 null这个方法能求有定位的父级
4.7.6 让滚动条滚动
js是前端学习的重中之重,内容很多,入门虽然简单但是常常会忽视掉一些底层的原理。学习js一定要注意底层原理,开发中可能一个不小心就给调试带来很大的麻烦。在学习的时候要求精,不要一味追求速度。