作用域:
ES5中的作用域有全局作用域、函数作用域;
ES6中新增了块级作用域,块级作用域由{}包括,if语句和for语句里面的{}也属于块作用域。
var: 1)没有块级作用域,有全局作用域、函数作用域的概念;2)不初始化默认值为undefined;3)存在变量提升;4)全局作用域用var声明的变量会挂在window对象下;5)统一作用域中允许重复声明;
console.log(a);
var a = 10;
这里存在一个变量提升的现象
所谓变量提升,就是将变量的声明部分提升到当前作用域的最顶端,上面的代码就等价于:
var a;
console.log(a);
a = 10;
let:1)有块级作用域的概念,在{}中声明的,全局中找不到;
2)不存在变量提升,无法在初始化之前访问,说明不存在变量提升;
3)暂时性死区:ES6规定,let/const命令会使区块形成封闭的作用域。若在声明之前使用变量,就会报错。总之,在代码块内,使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”;因为有暂时性死区的概念,所以它不存在变量提升的概念;
4)同一块作用域中不允许重复声明
const:与let特性一样,仅有两个差别:
1)必须立即初始化,不能留到以后赋值;
2)常量的值不能改变
在JS中,数据类型整体上来讲可以分为两大类:基本数据类型和引用数据类型;
基本数据类型分为6种:undefined、null、number、string、boolean、symbol(ES6新增的,独一无二的值)
引用数据类型只有1种:object
基本数据类型又称之为原始值或简单值,而引用数据类型的值又称之为复杂值或引用值;
简单值:简单值是表示JavaScript中可用的数据或信息的最底层形式或最简单形式。简单类型的值被称为简单值,是因为它们是不可细化的,也就是说,数字是数字,字符串是字符串,布尔值是true或false。这些值本身很简单,不能够再进行拆分;由于简单值的数据大小是固定的,所以简单值的数据是存储于内存中的栈区里面的。栈:先进后出,后进先出(类似于乒乓球盒子)。
console.log(typeof null);//object
这里面null比较特殊,由于历史遗留问题,是来源于JavaScript从第一个版本开始时的一个bug,并且这个bug无法被修复,因为修复会破坏现有的代码。
具体原因是因为不同的对象在底层都表现为二进制,在JavaScript中二进制前三位都为0的话会被判断为object类型,null的二进制全部为0,自然前三位也是0,所以执行typeof值会返回object。
另外,当我们打印null == undefined时,返回的是true,这个也是面试时经常会被问到的一个问题,这两个值都表示“无”的意思。通常情况下,当我们试图访问某个不存在的或者没有赋值的变量时,就会得到一个undefined值。JavaScript会自动将声明是没有进行初始化的变量设为undefined。而null值表示空,null不能通过JavaScript来自动赋值,也就是说必须要我们自己手动来给某个变量赋值为null ,那么为什么JavaScript要设置两个表示“无”的值呢:
1)null像在Java里一样,被当成一个对象。但是JavaScript的数据类型分成原始类型和复合类型两大类,作者觉得表示“无”的值最好不是对象
2)JavaScript的最初版本没有包括错误处理机制,发生数据类型不匹配时,往往是自动转换类型或者默默地失败。作者觉得,如果null自动转为0,很不容易发现错误。
因此,作者又设计了一个undefined。这里注意,先有null后有undefined出来,undefined是为了填补之前的坑。
null是一个表示“无”的对象(空对象指针),转为数值时为0;典型的用法:
1)作为函数的参数,表示该函数的参数不是对象;
2)作为对象原型链的终点。
undefined是一个表示“无”的原始值,转为数值时为NaN。典型的用法是:
1)变量被声明了,但没有赋值时,就等于undefined;
2)调用函数时,应该提供的参数没有提供,该参数就等于undefined;
3)对象没有赋值的属性,该属性的值为undefined;
4)函数没有返回值时,默认返回undefined。
复杂值:对象就是一个复杂值,因为对象可以向下拆分,拆分成多个简单值或者复杂值;复杂值在内存中的大小是未知的,因为复杂值可以包含任何值,而不是一个特定的已知值,所以复杂值的数据是存储于堆区里面。
区别如下:
1)访问方式:简单值是作为不可细化的值进行存储和使用的,是按值访问,引用他们会转移其值;复杂值是通过引用进行存储和操作的,而不是实际的值,创建一个包含复杂对象的变量时,其值是内存中的一个引用地址,引用一个复杂对象时,使用它的名称(即变量或对象属性)通过内存中的引用地址获取该对象值。
2)比较方式:简单值采用值比较,而复杂值采用引用比较。复杂值只有在引用相同的对象(即有相同的地址)时才相等。即使是包含相同对象的两个变量也彼此不相等,因为它们并不指向同一个对象。
3)动态属性:对于复杂值,可以为其添加属性和方法,也可以改变和删除其属性和方法。但是简单值不可以;
复杂值支持动态对象属性,因为我们可以定义对象,然后创建引用,再更新对象,并且所有指向该对象的变量都会获得更新。一个新变量指向现有的复杂对象,并没有复制该对象,这就是复杂值有时被称为引用值的原因。复杂值可以根据需求有任意多个引用,即使对象改变,它们也总是指向同一个对象。
4)变量赋值:简单值就是直接赋值,复杂值赋值时赋予的是地址。
在ES中,数据的分类为基本数据类型和引用类型;基本数据类型和引用类型这两个类型其中一个很明显的区别是:引用类型有自己内置的方法,也可以是自定义其他方法用来操作数据,而基本数据类型不能像引用类型那样有自己的内置方法对数据进行更多的操作。但基本数据类型中有3个是ES提供了对应的特殊引用类型(包装类)Boolean、Number、String,例如:
var str = 'hello';
var s2 = str.charAt(0);
console.log(s2);
这个string类型可以调用charAt()方法,其主要原因是在执行第二行代码时,后台会自动进行下面的步骤:
1)自动创建String类型的一个实例(和基本类型的值不同,这个实例就是一个基本包装类型的对象);
2)调用实例(对象)上指定的方法;
3)销毁这个实例;
var str = 'hello';//string 基本类型
var s2 = str.charAt(0);//在执行到这一句时,后台会自动完成以下动作:
(
var _str = new String('hello');//1、找到对应的包装对象类型,然后通过包装对象创建出一个和基本类型值相同的对象
var s2 = _str.charAt(0);//2、然后这个对象就可以调用包装对象下的方法,并且返回给s2
_str = null;//3、之后这个临时创建的对象就被销毁了,str = null
)
console.log(s2);//h
console.log(str);//hello
但是如果要读取基本数据类型的属性值时,结果为undefined,原因如下:
var i = 1;
i.test = "Hello";
console.log(i.test);
//当执行29行代码的时候,实际上后台执行了以下的操作:
var _i = new Number(1);
_i.test = "Hello";
_i.test = null;
如果直接声明的时候就是包装对象类型,那么是可以添加属性方法的,因为是一个对象。
如果真的需要在基础数据类型上添加属性,可以去它的原型链上进行添加。
JavaScript是一种动态类型语言,变量没有类型限制,可以随时赋予任意值。
var x = y ? 1 : 'a'
另外,JavaScript中存在数据类型转换:
'4' - '3' //1
数据转换分为强制转换(显示转换)和自动转换(隐式转换)
强制转换的方式有:Number()、String()、Boolean()
Number()
1)原始类型值转换:
//数值:转换后还是原来的值
Number(324)//324
//字符串:如果可以被解析为数值,则转换为相应的数值
Number('324')//324
//字符串,如果不可以被解析为数值,返回NaN
Number('324abc')//NaN
//空字符串转为0
Number('')//0
//布尔值:true转成1,false转成0
Number(true)
Number(false)
//undefined:转成NaN
Number(undefined)//NaN
//null转成0
Number(null)//0
Number函数将字符串转为数值,要比parseInt函数严格很多。基本上,只要有一个字符无法转成数值,整个字符创都会被转换为NaN。
parseInt('42 cats')//42
Number('42 cats')//NaN
上面代码中,parseInt逐个解析字符,而Number函数整体转换字符串的类型,另外,parseInt和Number函数都会自动过滤一个字符串前导和后缀的空格
parseInt('\t\v\r12.34\n')//12
Number('\t\v\r12.34\n')//12.34
2)对象
简单的规则是:Number方法的参数是对象时,将返回NaN,除非是包含单个数组的数值。
Number({a:1})//NaN
Number([1,2,3])//NaN
Number([5])//5
之所以会这样,是因为Number背后的转换规则比较复杂:
第一步,调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。
第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。
第三步,如果toString方法返回的是对象,就报错。
var obj = {x:1};
Number(obj)//NaN
//等同于
if (typeof obj.valueof() === 'object'){
Number(obj.toString());
}else{
Number(obj.valueof();
}
valueof 和 toString是任何对象或值都有的方法,因为他们是挂载在object.prototype上的方法
var obj = {
name:'haha'
};
console.log(Number(obj));//NaN
//1.valueof-->{name:'haha'}
//2.toString-->[object object]-->Number('[object object]')
//3.最终得到NaN
递归调用是一种特殊的调用形式,指的是方法自己调用自己的形式。例如:
function neverEnd(){
console.log("This is the method that never ends!")
neverEnd();
}
method会先输出This is the method that never ends!然后再调用自己,导致无限递归(infinite recursion)。当然这一般是我们需要避免的状况。
在进行递归操作的时候,我们需要满足以下几个条件:
1)递归调用必须有结束条件
2)每次调用的时候都需要根据需求改变传递的参数内容
例如,计算阶乘的函数:
function factorial(x){
if(x === 1){
return 1;
}else{
return x*factorial(x-1);
}
}
使用递归时需要注意如下事项:
1)递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以用循环的方式来实现。
2)使用递归时需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当一个函数调用,栈就会加一层,每当函数返回,栈就会减一层。由于栈的大小不是无限的,所以递归调用的次数过多,会导致栈溢出。
实例1:使用递归来计算从x加到y的结果
function calc(i,j){
if(i === j){
return i;
}else{
return calc(i,j-1) + j
}
}
console.log(calc(1,100))
实例2:使用递归来计算斐波那契数列:
function calc(i){
if(i == 1){
return 0;
}else if(i == 2){
return 1;
}else{
return calc(i - 1) + calc(i - 2);
}
console.log(calc(7));
}
calc(6)+calc(5)==>calc(5)+calc(4)+calc(4)+calc(3)
==>calc(4)+calc(3)+calc(3)+calc(2)+calc(3)+calc(2)+calc(2)+calc(1)
==>1+1+1+0+1+0+1+1+0+1+1+0
==>8
1)var定义的变量,没有块的概念,可以跨块访问,不能跨函数访问,有变量提升;
2)let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,无变量提升,不可以重复声明。
3)const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改,无变量提升,不可以重复声明。
另一种回答:
var 会挂载window,let、const不会;
var存在变量提升,let、const不会;
let、const存在块级作用域;
let、const同一作用域内不可声明同名变量,var可以。
最初在JS中作用域有:全局作用与、函数作用域。没有块级作用域的概念。ES6中新增了块级作用域,块级作用域由{}包括,if语句和for语句里面的{}也属于块级作用域;在以前没有块作用域的时候,在if或者for循环中声明的变量会泄漏成全局变量,其次就是{}中的内层变量可以能会覆盖外层变量。块级作用域的出现解决了这些问题。
在JS中,数据类型分为基本数据类型和引用数据类型;
基本数据类型为6种:undefined、null、number、string、boolean、symbol;其中symbol是ES6中新增的一种数据类型,表示独一无二的值。
引用数据类型只有1种:object。
两者的区别在于:原始值是表示JavaScript中可用的数据或者信息的最底层或最简单形式,是不可细化的,存储于栈里面;引用值在内存中大小是未知的,因为引用值可以包含任何值,而不是一个特定的已知值,所以引用值的数据都是存储与堆里面;
1)访问方式:原始值:访问到的是值;引用值:访问到的是引用地址
2)比较方式:原始值:比较的是值;引用值:比较的是地址
3)动态属性:原始值:无法添加动态属性;引用值:可以添加动态属性
4)变量赋值:原始值:赋值的是值;引用值:赋值的是地址。
包装对象,就是当基本类型以对象的方式去使用时,JavaScript会转换成对应的包装类型,相当于new一个对象,内容和基本类型的内容一样,然后当操作完成再去访问的时候,这个临时对象会被销毁,然后再访问的时候就是undefined。
number、string、boolean都有对应的包装类型。
因为有了基本包装类型,所以JavaScript中的基本类型值可以被当做对象来访问;
基本特征:
1)每个包装类型都映射到同名的基本类型;
2)在读取模式下访问基本类型值时,就会创建对应的基本包装类型的一个对象,从而方便了数据操作;
3)操作基本类型值的语句一经执行完毕,就会立即销毁新创建的包装对象。
1、使用递归完成1到100的累加:
function calc(i,j){
if(i === j){
return i;
}else{
return calc(i,j-1) + j;
}
}
console.log calc(1,100)
强制转换(显示转换):强制转换主要指使用Number()、String()、Boolean()三个函数,手动将各种类型的值,分别转换成数字、字符串或者布尔值。
Number():使用Number()函数,可以将任意类型的值转化成数值。
1)简单值
console.log(Number('123'))//123
console.log(Number('xiexie'))//NaN
console.log(Number('123xiexie'))//NaN
//Number()和parseInt就不一样,parseInt是会尽可能的去多转换
console.log(parseInt('123xiexie'))//123
console.log(parseInt('xiexie123'))//NaN
console.log(Number(true))//1
console.log(Number(false))//0
console.log(Number(undefined))//NaN
console.log(Number(null))//0
我感觉他们主要的作用都是改变this的指向,就是在用法上不一样,call和apply是立即执行的,主要区别就是在传递参数上不同,call后面传递的参数是以逗号的形式分开的,apply传递的参数是以数组形式【Apply是以A开头的,所以应该是跟Array形式的传参】,bind返回的是一个函数形式,如果要执行,则后面要再加一个小括号;例如bind(obj,参数1,参数2)(),bind只能以逗号分隔形式,不能以数组形式。
在对数组或对象进行遍历时,我们经常会使用到两种方法:for in和for of,那么这两种方法之间的区别就是它们两者都可以用于遍历,不过for in遍历的是数组的索引(index),而for of遍历的是数组元素值(value);
for in 更适合遍历对象,当然也可以遍历数组,但是会存在一些问题。
每个函数都有prototype属性,该属性指向原型对象,使用原型对象的好处是所有对象实例共享它所包含的属性和方法,原型链:访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会沿着它的__proto__属性所指那个对象(父对象)里找,直到为null为止,这样一层一层的就构成了原型链,主要解决了继承的问题。
就是一个高阶函数的使用方法,假如一个函数接收三个参数并且计算结果输出,柯里化之后就是每个函数里只接收一个值,并且把接收的结果返回;
第一个优点是入口单一,易于测试与复用;第二个就是易于定位测试bug,可以准确定位到哪个参数在哪个环节出了问题;
坏处就是函数嵌套函数,嵌套的层级太多了,占用内存、效率低,毕竟一个function都会生成个单独的作用域,都会在调用栈中占据一块内存空间,这个就是我理解柯里化的主要内容。
现在看来的话我感觉主流的框架都是MVVM模式的,比方说我项目中经常用的vue,MVVM框架里面M的话就是数据层,V就是View视图层,中间通过一个viewmodel进行了一个双向绑定,里面是通过Object.defineProperty对当前组件中的data所有属性添加了getter和setter方法,形成了数据驱动视图,而且视图层和数据层就相互分离,便于维护了,只要修改了数据层视图层就会发生变化,如果不变的话,可以使用this.$set方法来进行。
MVC是一种网站开发模型,model、view、controller;
model:进行业务逻辑的处理;view:视图,负责数据的输出和这个画面的展示;controller:控制层,负责接收从视图发送过来的数据,同事控制model和view;
我对他的理解就是所有的请求都先到控制层,然后进行不同的处理,再到不同的业务处理层,处理完之后返回数据,然后生成view视图,之后再返回给请求者,大概是这样的一个过程,MVC就像jquery,大量操作dom。
JS事件代理就是通过给父级元素(例如ul)绑定事件,不给子级元素(例如li)绑定事件,然后当点击子级元素时,通过事件冒泡机制在其绑定的父元素上触发事件处理函数,主要目的是为了提升性能,因为我不用给每个子级元素绑定事件,只给父级元素绑定一次就好了,在原生js里面就是通过event对象的target属性实现。
1)let const
2)箭头函数
3)解构赋值
4)模板字符串
5)class类语法糖;在ES6中,class类作为对象的模板被引入,可以通过class关键字定义类,class本质是function,它可以看作是一个语法糖,让对象原型的写法更加清晰,更像面向对象编程的语法。
6)import、export模块化开发
7)内置对象新增API
Array:
Array.from():返回数组,该方法可以将类数组对象转换成数组结构;
Array.prototype.includes():判断数组是否包含某值;
Array.prototype.fill():数组的填充方法;
String:
String.prototype.startsWith():判断字符串是否以括号里的内容开头的,返回值是boolean值;
String.prototype.endsWith():判断字符串是否以括号里的内容结尾,返回值时boolean值。
1)一些定时器,事件监听要在销毁期生命钩子函数中结束掉;
2)公共组件的封装,公共过滤方法的封装;
3)图片懒加载、对图片的压缩:监听滚动条高度,设置图片大小属性;当时是用了一个loders,在webpack里面进行配置,然后再vue.config.js里面开启了gzip压缩,让打包的资源体积进一步减小,然后使用cdn的缓存,同时把那些不会改变的包使用cdn缓存,不打包到整体的生产包里面去,他里面有那个externals的一个选项把他分离出去。
4)路由懒加载:const 组件名 = () => import('组件路径');使用webpack的chunkname进行一个分包加载,生产包打包的小一点,减少map文件的生成。
5)第三方库按需引入
6)Webpack对图片进行压缩,对有些较大的图片资源,在请求资源的时候,加载会很慢,我们可以用image-webpack-loader来压缩图片。
进程:当一个程序开始运行的时候,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源,而一个进程是由多个线程组成的;
线程:线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
多线程:指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程,来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务;
好处:可以提高CPU的利用率,在多线程程序中,一个线程必须等待的时候,CPU可以运行其他的线程而不是等待,这样就大大提高了程序的效率。
cookie内存大小只有4kb,可以设置失效时间,但没有自己的存取方法,需要封装,每次请求时在请求头里跟随请求发送,一般存一些加密的密码,token值一般存在这里,可以使用js-cookie插件实现;
本地存储有5mb,localStorage和sessionStorage可以有自己存取的方法,例如:setItem(),getItem(),removeItem(),clear();localStorage关闭页面不会消失,除非去removeItem;sessionStorage关闭页面就消失了,相当于临时性存储。
公司项目如何进行管理的?主要通过git进行项目版本控制;
说几个常用的git命令:git add. \git commit -m " "\git push \git pull \git stash \git log\git checkout;
多人操作统一文档,产生冲突如何解决?当遇到多人协作修改,我会先git pull下来,手动修改代码冲突后,再git add. \git commit -m " "\git push 上传到远端仓库;如果pull也pull不下来,提示冲突的话,可以先通过git stash暂存下来,然后再pull拉取,然后git stash pop,取出原来写的,手动修改再提交。
强缓存不经过服务器,协商缓存需要经过服务器。协商缓存返回的状态码是304,两类缓存机制可以同时存在,强缓存的优先级高于协商缓存。当执行强缓存时,如果缓存命中,则直接使用缓存数据中的数据,不再进行协商缓存。
当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header 验证这个资源是否命中协商缓存。如果协商缓存命中,服务器会将这个请求返回,但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器就又会从自己的缓存中去加载这个资源。
强缓存命中时,返回的http状态为200,在Google浏览器的开发者工具的Network里面的size会显示memory cache。
利用 Expries(过期时间) 或者cache-control(缓存相对时间)这两个HTTP RESPONSE 实现的都用来表示资源在客户端缓存的有效期。
协商缓存如果命中,请求响应返回的http状态为304,并且会显示一个Not Modified的字符串。利用Last-Modified、If-Modified-Since(值为上一个Last-Modified的值)控制协商缓存。
使用 Last-Modified (跟时间)有两个弊端:
如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
ETag、 If-None-Match控制协商缓存。**ETag 的优先级要高于 Last-Modified。**浏览器第一次跟服务器请求一个资源,服务器返回这个资源的同时,会在response的header加上ETag的header,这个header是服务器根据当前请求的资源生成的一个唯一的标识,是一个字符串,只要资源有变化,Etag 就会重新生成。浏览器再次跟服务器请求,则会在request的header加上一个 If-None-Match的header,这个header的值就是上一次请求返回的ETag值。如果服务器发现 ETag 匹配不上,那么直接以常规 GET 200回包形式将新的资源(当然也包括了新的 ETag)发给客户端;如果 ETag 是一致的,则直接返回304知会客户端直接使用本地缓存即可。
不同的网站应该有不同的缓存策略,应该结合网站的业务来制定适合的缓存策略。
对于基于webpack打包构建生成的spa项目
1)index.html 不做缓存,每次请求都获取最新版本;
2)使用 webpack 等 build 后(以文件内容为hash生成文件后缀)的其他所有资源文件(包括 js、css 和图片等),都做强缓存(一个月打底,可以设置一年)
cache-control: max-age=315360000;
3)对于经常发生变化的静态资源,可以设置etag来使用协商缓存;
防抖:防止抖动,避免事件重复触发,比如说监听输入框的输入,不应该每次都去触发监听,应该是用户完成一段输入后在进行触发。
function debounce(fn, delay){
let timer = null;
return function(){
clearTimeout(timer);
timer = setTimeout(()=> {
fn.apply(this, arguments);
}, delay)
}
}
设计思路:事件触发后开启一个定时器,如果事件在这个定时器限定的时间内再次触发,则清除定时器,在写一个定时器,定时时间到则触发。
节流:减少流量,将频繁触发的事件减少,并每隔一段时间执行。即控制事件触发的频率,比如发送短信验证码。
function throttle(fn, delay){
let valid = true;
return function(){
if(valid) {
setTimeout(()=> {
fn.apply(this, arguments);
valid = true;
}, delay)
valid = false;
}
}
}
设计思路:我们可以设计一种类似控制阀门一样定期开放的函数,事件触发时让函数执行一次,然后关闭这个阀门,过了一段时间后再将这个阀门打开,再次触发事件。
防抖和节流相同点:
防抖和节流都是为了阻止操作高频触发,从而浪费性能。
防抖和节流区别:
防抖是触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。适用于可以多次触发但触发只生效最后一次的场景。
节流是高频事件触发,但在n秒内只会执行一次,如果n秒内触发多次函数,只有一次生效,节流会稀释函数的执行频率。
DOM
DOM是文档对象模型,它指的是把文档当作一个对象来对待,这个对象主要定义了处理网页的内容和接口
BOM
BOM是浏览器对象模型,它指的是将浏览器当作一个对象来对待,这个对象主要定义了与浏览器进行交互的方法和接口;
BOM的核心是window,而window对象具有双重角色,它既是js访问浏览器窗口的一个接口,又是一个全局对象(Global);
这就意味着网页中定义的任何对象、变量和函数,都会作为全局对象的一个属性或者方法存在;
使用标准
1)DOM是W3C的标准,所有浏览器公共遵守的标准
2)BOM是各个浏览器厂商根据DOM在各自浏览器上的实现(表现为不同浏览器定义有差别,实现方式不同)
3)window为BOM对象,而非js对象
包含属性
BOM: location、navigato、document
DOM:document也是BOM的window的子对象;
PS:DOM的最根本的对象是BOM的window对象的子对象
Document
1)Object.keys():用于返回一个数组,该数组包含我们作为参数传递给他的特定对象的所有键;
由于对象不具备长度,你也可以使用Object.keys().length来获取对象的长度;
2)Object.freeze():该方法防止对象中的数据突变,使用此方法会使对象不能更新、新增或删除属性;
3)Object.seal():该方法和Object.freeze()方法很像,但是Object.seal()是可以更新已有属性,不可以新增或删除已有属性