DOM事件流
DOM的结构是一个树形,每当HTML元素产生事件时,该事件就会在树的根节点和元素节点之间传播,所有经过的节点都会收到该事件。
DOM事件模型分为两类:
冒泡型事件处理模型(Bubbling)
冒泡型事件处理模型在事件发生时,首先在最精确的元素上触发,然后向上传播,直到根节点。反映到DOM树上就是事件从叶子节点传播到根节点。
捕获型事件处理模型(Captrue)
相反地,捕获型在事件发生时首先在最顶级的元素上触发,传播到最低级的元素上。在DOM树上的表现就是由根节点传播到叶子节点。
标准的DOM事件处理模型
标准的事件处理模型分为三个阶段:
Event事件的常见api方法
自定义事件
var myEvent = new Event('clickTest');
element.addEventListener('clickTest', function () {
console.log('smyhvae');
});
//元素注册事件
element.dispatchEvent(myEvent); //注意,参数是写事件对象 myEvent,不是写 事件名 clickTest
事件委托的概念
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。 事件委托就是方法的代理。 可以简单理解为自己执行的事件可以让别的元素代替执行。
**事件委托的原理: **
事件委托是利用事件的冒泡原理来实现的 ,就是事件从最深的节点(目标节点)开始,然后逐步向上传播事件
事件委托的优点:
案例:
使用原始方法:
下面的代码的意思很简单,相信很多人都是这么实现的,我们看看有多少次的dom操作,首先要找到ul,然后遍历li,然后点击li的时候,又要找一次目标的li的位置,才能执行最后的操作,每次点击都要找一次li;
## 子节点实现相同的功能:
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
## 实现功能是点击li,弹出123:
window.onload = function(){
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('li');
for(var i=0;i<aLi.length;i++){
aLi[i].onclick = function(){
alert(123);
}
}
}
用事件委托的方式
window.onload = function(){
var oUl = document.getElementById("ul1");
oUl.onclick = function(){
alert(123);
}
}
这里用父级ul做事件处理,当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发,当然,这里当点击ul的时候,也是会触发的,那么问题就来了,如果我想让事件代理的效果跟直接给节点的事件效果一样怎么办,比如说只有点击li才会触发,不怕,我们有绝招:
Event对象提供了一个属性叫target,可以返回事件的目标节点,我们成为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,当然,这个是有兼容性的,标准浏览器用ev.target,IE浏览器用event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,这里我们用nodeName来获取具体是什么标签名,这个返回的是一个大写的,我们需要转成小写再做比较(习惯问题):
window.onload = function(){
var oUl = document.getElementById("ul1");
oUl.onclick = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
alert(123);
alert(target.innerHTML);
}
}
}
var obj11 = {name: 'smyh'};
var obj12 = new Object(name: `smyh`); //内置对象(内置的构造函数)
# 上面的两种写法,效果是一样的。因为,第一种写法,obj11会指向Object。
第一种写法是:字面量的方式。
第二种写法是:内置的构造函数
var M = function (name) {
this.name = name;
}
var obj3 = new M('smyhvae');
var p = {name:'smyhvae'};
var obj3 = Object.create(p); //此方法创建的对象,是用原型链连接的
# 这种方式里,obj3是实例,p是obj3的原型(name是p原型里的属性),构造函数是Objecet 。
function createPerson(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
alert(this.name);
};
return 0;
}
var person1=createPerson("Nicholas",29,"Software Engineer");
var person2=createPerson("Greg",27,"Doctor");
## 与工厂模式相比,具有以下特点:
没有显式创建对象;
直接将属性和方法赋给了this对象;
没有return语句;
要创建新实例,必须使用new操作符;(否则属性和方法将会被添加到window对象)
可以使用instanceof操作符检测对象类型
## 构造函数的问题:
构造函数内部的方法会被重复创建,不同实例内的同名函数是不相等的。可通过将方法移到构造函数外部解决这一问题,但面临新问题:封装性不好。
这些问题可通过原型模式解决。
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="...";
Person.prototype.sayName=function(){
...
};
var person1=new Person();
person1.sayName();//"Nicholas"
**方式一:**用构造函数模拟类(传统写法)
function Animal1() {
this.name = 'smyhvae'; //通过this,表明这是一个构造函数
}
**方式二:**用 class 声明(ES6的写法)
class Animal2 {
constructor() { //可以在构造函数里写属性
this.name = name;
}
}
类的实例化很简单,直接 new 出来即可。
console.log(new Animal1(),new Animal2()); //实例化。如果括号里没有参数,则括号可以省略
function Parent(){
this.name = 'parent的属性'
}
function Child(){
Parent.call(this);
this.type = 'child的属性'
}
console.log(new Child)
【重要】上方代码中,最重要的那行代码:在子类的构造函数里写了Parent1.call(this);
,意思是:让Parent的构造函数在child的构造函数中执行。发生的变化是:改变this的指向,parent的实例 --> 改为指向child的实例。导致 parent的实例的属性挂在到了child的实例上,这就实现了继承。
**缺点:**这种方式,虽然改变了 this 的指向,但是,Child1 无法继承 Parent1 的原型。也就是说,如果我给 Parent1 的原型增加一个方法,是无法被继承的。
function Parent(){
this.name = 'parent的属性'
}
function Child(){
this.type = 'child的属性'
}
Child.prototype = new Parent()
console.log(new Child)
【重要】上方代码中,最重要的那行:每个函数都有prototype
属性,于是,构造函数也有这个属性,这个属性是一个对象。现在,我们把Parent的实例赋值给了Child的prototye,从而实现继承。此时,Child
构造函数、Parent
的实例、Child
的实例构成一个三角关系。于是:
new Child.__proto__ === new Parent()
的结果为true分析:
这种继承方式,Child 可以继承 Parent 的原型,但有个缺点:
缺点是:如果修改 child1实例的name属性,child2实例中的name属性也会跟着改变。
function Parent3() {
this.name = 'Parent 的属性';
this.arr = [1, 2, 3];
}
function Child3() {
Parent3.call(this); //【重要1】执行 parent方法
this.type = 'Child 的属性';
}
Child3.prototype = new Parent3(); //【重要2】第二次执行parent方法
var child = new Child3();
这种方式,能解决之前两种方式的问题:既可以继承父类原型的内容,也不会造成原型里属性的修改。
这种方式的缺点是:让父亲Parent的构造方法执行了两次。
class Person {
constructor(skin, language) {
this.skin = skin;
this.language = language;
}
say() {
console.log('I am a Person, ' + 'skin: ' + this.skin + 'language: ' + this.language)
}
}
class American extends Person {
aboutMe() {
console.log(this.skin + ' ' + this.language)
}
}
var american = new American('yello', 'chinese')
console.log(american.skin) // yello
american.say() // I am a Person, skin: yellolanguage: chinese
american.aboutMe() // yello chinese
程序:由源代码生成的可执行应用。 (例如:QQ.app)
进程:一个正在运行的程序可以看做一个进程,(例如:正在运行的QQ警示),进程拥有独立运行所需要的全部资源
线程:程序中独立运行的代码段。(例如:接收QQ消息的代码)
一个进程是由一或多个线程组成,进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。
单线程
多线程
单线程程序:只有一个线程,代码顺序执行,容易出现代码阻塞(页面假死)
多线程程序:有多个线程,线程间独立运行,能有效地避免代码阻塞,并且提高程序的运行性能
**多线程与单线程的区别**
生活举例
你早上上班,正要打卡的时候,手机响了。。你如果先接了电话,等接完了,在打卡,就是单线程。
如果你一手接电话,一手打卡。就是多线程。
2件事的结果是一样的。。你接了电话且打了卡。
因为是单线程,所以必须异步。
# 现有如下代码:
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000);
console.log(3);
console.log(4);
# 上面的代码中,我们很容易知道,打印的顺序是1,3,4,2。因为你会想到,要等一秒之后再打印2。
# 可如果我把延时的时间从1000改成0:
console.log(1);
setTimeout(function () {
console.log(2);
}, 0);
console.log(3);
console.log(4);
# 上方代码中,打印的顺序仍然是1,3,4,2。这是为什么呢?我们来分析一下。
# 总结:
# js 是单线程(同一时间只能做一件事),而且有一个任务队列:全部的同步任务执行完毕后,再来执行异步任务。第一行代码和最后一行代码是同步任务;但是,setTimeout是异步任务。
于是,执行的顺序是:
先执行同步任务console.log(1)
遇到异步任务setTimeout,要挂起
执行同步任务console.log(3)
全部的同步任务执行完毕后,再来执行异步任务console.log(2)。
很多人会把这个题目答错,这是因为他们不懂 js 的运行机制。
注意上面那句话:同步任务执行完毕后,再来执行异步任务。也就是说,如果同步任务没有执行完,异步任务是不会执行的。为了解释这句话,我们来看下面这个例子。
console.log('A');
alert('haha'); //1秒之后点击确认
console.log('B');
# alert函数是同步任务,我只有点击了确认,才会继续打印B。
我们在上面列举了异步和同步的例子。现在来描述一下区别:【重要】
因为setTimeout
是异步任务,所以程序并不会卡在那里,而是继续向下执行(即使settimeout设置了倒计时一万秒);但是alert
函数是同步任务,程序会卡在那里,如果它没有执行,后面的也不会执行(卡在那里,自然也就造成了阻塞)。
什么时候需要等待,就什么时候用异步。
加载代码举例:
console.log('start');
var img = document.createElement('img');
img.onload = function () {
console.log('loaded');
}
img.src = '/xxx.png';
console.log('end');
上图中,先打印start
,然后执行img.src = '/xxx.png'
,然后打印end
,最后打印loaded
。
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
总结:只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。【重要】
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
事件循环是如何执行的,事件循环器会不停的检查事件队列,如果不为空,则取出队首压入执行栈执行。当一个任务执行完毕之后,事件循环器又会继续不停的检查事件队列,不过在这期间,浏览器会对页面进行渲染。
关于事件循环,我们需要记住以下几点:
案例1:
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
# 很多人以为上面的题目,答案是0,1,2,3。其实,正确的答案是:3,3,3,3。
案例2:
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
console.log(i);
# 循环执行过程中,几乎同时设置了 3 个定时器,这些定时器都会在 1 秒之后触发,而循环完的输出是立即执行的.
# 3 -> 3,3,3,即第 1 个 3 直接输出,1 秒之后,连续输出 3 个 3。
我们经常会用ES6中的Class来代替JS中的构造函数做开发。
请写出函数实现
typeof(obj) === “string”
typeof obj === “string”
obj.constructor === String
# 方法一:使用replace正则匹配的方法
去除所有空格: str = str.replace(/s*/g,"");
去除两头空格: str = str.replace(/^s*|s*$/g,"");
去除左空格: str = str.replace( /^s*/, “”);
去除右空格: str = str.replace(/(s*$)/g, “”);
str为要去除空格的字符串,实例如下:
var str = " 23 23 “;
var str2 = str.replace(/s*/g,”");
console.log(str2); // 2323
# 方法二:使用str.trim()方法
str.trim()局限性:无法去除中间的空格,实例如下:
var str = " xiao ming ";
var str2 = str.trim();
console.log(str2); //xiao ming
同理,str.trimLeft(),str.trimRight()分别用于去除字符串左右空格。
# 方法三:使用jquery,$.trim(str)方法
$.trim(str)局限性:无法去除中间的空格,实例如下:
var str = " xiao ming ";
var str2 = $.trim(str)
console.log(str2); // xiao ming
我这里只是列举了常用的字符串函数,具体使用方法,请参考网址。
concat() – 将两个或多个字符的文本组合起来,返回一个新的字符串。
indexOf() – 返回字符串中一个子串第一处出现的索引。如果没有匹配项,返回 -1 。
charAt() – 返回指定位置的字符。
lastIndexOf() – 返回字符串中一个子串最后一处出现的索引,如果没有匹配项,返回 -1 。
match() – 检查一个字符串是否匹配一个正则表达式。
substr() 函数 – 返回从string的startPos位置,长度为length的字符串
substring() – 返回字符串的一个子串。传入参数是起始位置和结束位置。
slice() – 提取字符串的一部分,并返回一个新字符串。
replace() – 用来查找匹配一个正则表达式的字符串,然后使用新字符串代替匹配的字符串。
search() – 执行一个正则表达式匹配查找。如果查找成功,返回字符串中匹配的索引值。否则返回 -1 。
split() – 通过将字符串划分成子串,将一个字符串做成一个字符串数组。
length – 返回字符串的长度,所谓字符串的长度是指其包含的字符的个数。
toLowerCase() – 将整个字符串转成小写字母。
toUpperCase() – 将整个字符串转成大写字母。
function showWindowHref(){
var sHref = window.location.href;
var args = sHref.split('?');
if(args[0] == sHref){
return "";
}
var arr = args[1].split('&');
var obj = {};
for(var i = 0;i< arr.length;i++){
var arg = arr[i].split('=');
obj[arg[0]] = arg[1];
}
return obj;
}
var href = showWindowHref(); // obj
console.log(href['name']); // xiaoming
1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
2)添加、移除、替换、插入
appendChild() //添加
removeChild() //移除
replaceChild() //替换
insertBefore() //插入
3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性
JavaScript 中 typeof 和 instanceof 常用来判断一个变量是否为空,或者是什么类型的。
typeof
typeof 其实就是判断参数是什么类型的实例,就一个参数
typeof 一般只能返回如下几个结果:“number”、“string”、“boolean”、“object”、“function” 和 “undefined”。
在 JavaScript 中,判断一个变量的类型尝尝会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 “object”。这就需要用到instanceof来检测某个对象是不是另一个对象的实例。
instanceof
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
参数:
object(
要检测的对象.)constructor(
某个构造函数)
使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念
闭包是指有权访问另一个函数作用域中的变量的函数. 创建闭包常见方式,就是在一个函数内部创建另一个函数.
应用场景 设置私有变量和方法
不适合场景:返回闭包的函数是个非常大的函数
闭包的缺点就是常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
为什么会出现闭包这种东西,解决了什么问题?
受JavaScript链式作用域结构的影响,父级变量中无法访问到子级的变量值,为了解决这个问题,才使用闭包这个概念
共同点:用于浏览器端存储的缓存数据
不同点:
(1)、存储内容是否发送到服务器端:当设置了Cookie后,数据会发送到服务器端,造成一定的宽带浪费;
web storage,会将数据保存到本地,不会造成宽带浪费;
(2)、数据存储大小不同:Cookie数据不能超过4K,适用于会话标识;web storage数据存储可以达到5M;
(3)、数据存储的有效期限不同:cookie只在设置了Cookid过期时间之前一直有效,即使关闭窗口或者浏览器;
sessionStorage,仅在关闭浏览器之前有效;localStorage,数据存储永久有效;
(4)、作用域不同:cookie和localStorage是在同源同窗口中都是共享的;sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;
简单数据类型:Undefined、Null、Boolean、Number 和String。 复杂数据类型:Object
前者会自动转换类型,而后者不会。 前者比较的是值,后者比较的是值和类型。
# Arguments 函数参数集合
arguments[ ] 函数参数的数组
Arguments 一个函数的参数和其他属性
Arguments.callee 当前正在运行的函数
Arguments.length 传递给函数的参数的个数
# Array 数组
length属性动态获取数组长度
join() 将一个数组转成字符串。返回一个字符串。
reverse() 将数组中各元素颠倒顺序
delete运算符 只能删除数组元素的值,而所占空间还在,总长度没变(arr.length)。
shift() 删除数组中第一个元素,返回删除的那个值,并将长度减 1。
pop() 删除数组中最后一个元素,返回删除的那个值,并将长度减1。
unshift() 往数组前面添加一个或多个数组元素,长度要改变。
push() 往数组结尾添加一个或多个数组元素,长度要改变。
concat( ) 连接数组
slice( ) 返回数组的一部分
sort( ) 对数组元素进行排序
splice( ) 插入、删除或替换数组的元素
toLocaleString( ) 把数组转换成局部字符串
toString( ) 将数组转换成一个字符串
# Boolean 布尔对象
Boolean.toString( ) 将布尔值转换成字符串
Boolean.valueOf( ) Boolean对象的布尔值
# Error 异常对象
Error.message 可以读取的错误消息
Error.name 错误的类型
Error.toString( ) 把Error 对象转换成字符串
EvalError 在不正确使用 eval()时抛出
SyntaxError 抛出该错误用来通知语法错误
RangeError 在数字超出合法范围时抛出
ReferenceError 在读取不存在的变量时抛出
TypeError 当一个值的类型错误时,抛出该异常
URIError 由URl的编码和解码方法抛出
# Function 函数构造器
Function.apply( ) 将函数作为一个对象的方法调用
Function.arguments[] 传递给函数的参数
Function.call( ) 将函数作为对象的方法调用
Function.caller 调用当前函数的函数
Function.length 已声明的参数的个数
Function.prototype 对象类的原型
Function.toString( ) 把函数转换成字符串
# Math 数学对象
Math对象是一个静态对象
Math.PI 圆周率。
Math.abs() 绝对值。
Math.ceil() 向上取整(整数加 1,小数去掉)。
Math.floor() 向下取整(直接去掉小数)。
Math.round() 四舍五入。
Math.pow(x,y) 求 x的y次方。
Math.sqrt() 求平方根。
# Number 数值对象
Number.MAX_VALUE 最大数值
Number.MIN_VALUE 最小数值
Number.NaN 特殊的非数字值
Number.NEGATIVE_INFINITY 负无穷大
Number.POSITIVE_INFINITY 正无穷大
Number.toExponential( ) 用指数计数法格式化数字
Number.toFixed( ) 采用定点计数法格式化数字
Number.toLocaleString( ) 把数字转换成本地格式的字符串
Number.toPrecision( ) 格式化数字的有效位
Number.toString( ) 将—个数字转换成字符串
Number.valueOf( ) 返回原始数值
# Object 基础对象
Object 含有所有 JavaScript 对象的特性的超类
Object.constructor 对象的构造函数
Object.hasOwnProperty( ) 检查属性是否被继承
Object.isPrototypeOf( ) 一个对象是否是另一个对象的原型
Object.propertyIsEnumerable( ) 是否可以通过 for/in循环看到属性
Object.toLocaleString( ) 返回对象的本地字符串表示
Object.toString( ) 定义一个对象的字符串表示
Object.valueOf( ) 指定对象的原始值
# RegExp 正则表达式对象
RegExp.exec( ) 通用的匹配模式
RegExp.global 正则表达式是否全局匹配
RegExp.ignoreCase 正则表达式是否区分大小写
RegExp.lastIndex 下次匹配的起始位置
RegExp.source 正则表达式的文本
RegExp.test( ) 检测一个字符串是否匹配某个模式
RegExp.toString( ) 把正则表达式转换成字符串
# String 字符串对象
Length 获取字符串的长度。
toLowerCase() 将字符串中的字母转成全小写。
toUpperCase() 将字符串中的字母转成全大写。
charAt(index) 返回指定下标位置的一个字符。如果没有找到,则返回空字符串。
substr() 在原始字符串,返回一个子字符串
substring() 在原始字符串,返回一个子字符串。
split() 将一个字符串转成数组。
charCodeAt( ) 返回字符串中的第 n个字符的代码
concat( ) 连接字符串
fromCharCode( ) 从字符编码创建—个字符串
indexOf( ) 返回一个子字符串在原始字符串中的索引值(查找顺序从左往右查找)。如果没 有找到,则返回-1。
lastIndexOf( ) 从后向前检索一个字符串
localeCompare( ) 用本地特定的顺序来比较两个字符串
match( ) 找到一个或多个正则表达式的匹配
replace( ) 替换一个与正则表达式匹配的子串
search( ) 检索与正则表达式相匹配的子串
slice( ) 抽取一个子串
toLocaleLowerCase( ) 把字符串转换小写
toLocaleUpperCase( ) 将字符串转换成大写
toLowerCase( ) 将字符串转换成小写
toString( ) 返回字符串
toUpperCase( ) 将字符串转换成大写
valueOf( ) 返回字符串
短路表达式只是一种简写形式,也就是用 && 和 || 来赋值或者执行函数的形式
例如:
var foo = foo1 || foo2;
意思是如果foo1是真的,那么就把foo1的值赋给foo,否则把foo2的值赋给foo。
foo && foo()
当foo存在的时候,我们就执行foo函数,如果这个时候foo不是一个函数,就会报错,所以这个只是一种简写形式而已。
在Javascript 中,如果一个对象不再被引用,那么这个对象就会被GC 回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。 因为函数 a被b引用,b又被 a外的 c引用,这就是为什么 函数 a 执行后不会被回收的原 因。
内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。
垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。
setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)。
function whatBrowser() {
//获取完整的浏览器名称
document.Browser.Name.value=navigator.appName;
//获取浏览器的版本,一般不与实际的浏览器版本对应
document.Browser.Version.value=navigator.appVersion;
//获取浏览器的名称。通常都是Mozilla,即使在非Mozilla的浏览器中也是如此
document.Browser.Code.value=navigator.appCodeName;
//获取浏览器的用户代理字符串
document.Browser.Agent.value=navigator.userAgent;
}
(1)创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
(2)属性和方法被加入到 this 引用的对象中。
(3)新创建的对象由 this 所引用,并且最后隐式的返回 this 。
答:this总是指向函数的直接调用者(而非间接调用者);
如果有new关键字,this指向new出来的那个对象;
在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window。
null是一个表示"无"的对象,转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。
undefined:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。
null:
(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。
它的功能是把对应的字符串解析成JS代码并运行;
应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。
由JSON字符串转换为JSON对象的时候可以用eval,var obj =eval(’(’+ str +’)’)。
答: Undefined、Null、Boolean、Number、String
答:数据封装类对象:Object、Array、Boolean、Number 和 String
其他对象:Function、Arguments、Math、Date、RegExp、Error
共同点:这两种事件都代表的是页面文档加载时触发。
异同点:
ready 事件的触发,表示文档结构已经加载完成(不包含图片等非文字媒体文件)。
onload 事件的触发,表示页面包含图片等文件在内的所有元素都加载完成。
var arr = [2,3,4,4,5,2,3,6],
arr2 = [];
for(var i = 0;i< arr.length;i++){
if(arr2.indexOf(arr[i]) < 0){
arr2.push(arr[i]);
}
}
console.log(arr2);
结果为:[2, 3, 4, 5, 6]
1、定义和用法:当一个函数的返回值是另外一个函数,而返回的那个函数如果调用了其父函数内部的其它变量,如果返回的这个函数在外部被执行,就产生了闭包。
2、表现形式:使函数外部能够调用函数内部定义的变量。
3、实例如下:
(1)、根据作用域链的规则,底层作用域没有声明的变量,会向上一级找,找到就返回,没找到就一直找,直到window的变量,没有就返回undefined。这里明显count 是函数内部的flag2 的那个count 。
var count=10; //全局作用域 标记为flag1
function add(){
var count=0; //函数全局作用域 标记为flag2
return function(){
count+=1; //函数的内部作用域
alert(count);
}
}
var s = add()
s();//输出1
s();//输出2
要理解闭包,首先必须理解Javascript特殊的变量作用域。
变量的作用域分类:全局变量和局部变量。
特点:
1、函数内部可以读取函数外部的全局变量;在函数外部无法读取函数内的局部变量。
2、函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
5、使用闭包的注意点
1)滥用闭包,会造成内存泄漏:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)会改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window
对象即被终止,作用域链向下访问变量是不被允许的。
栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。
队列先进先出,栈先进后出。
栈只允许在表尾一端进行插入和删除,而队列只允许在表尾一端进行插入,在表头一端进行删除
12345
栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。
堆(数据结构):堆可以被看成是一棵树,如:堆排序;
Var btn=document.getElementById(‘btn’);
//事件监听 绑定多个事件
var btn4 = document.getElementById("btn4");
btn4.addEventListener("click",hello1);
btn4.addEventListener("click",hello2);
function hello1(){
alert("hello 1");
}
function hello2(){
alert("hello 2");
}
var arr1=[1,2,3,4,5,6,7,8,9];
var sum1=0;
for (var i=0;i<arr1.length;i++) {
if (typeof arr1[i]=="number") {
sum1+=arr1[i];
}
}
document.write(sum1);
.getAttribute(``"属性名"``)``//获取
.setAttribute(``"属性名"``,``"值"``)``//设置
事件委托就是利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
<script>
window.onload = function () {
var oUl = document.getElementById('ul1');
oUl.onclick = function (ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li') {
alert(target.innerHTML)
}
}
}
</script>
Object.is()类似于===,但在三等号判等的基础上特别处理了 NaN 、-0 和 +0 ,保证 -0 和 +0 不再相同,但 Object.is(NaN, NaN) 会返回 true。
defer属性、async属性、动态创建、jquery的getScript()
hasOwnProperty
原生对象:
独立于宿主环境的ECMAScript实现提供的对象。与宿主无关,在javascript(远景浏览器)、nodejs(node平台)、jscript(ie浏览器)、typescript(微软平台)等等中均有这些对象。简单来说,本地对象就是 ECMA-262 定义的类(引用类型)。在运行过程中动态创建的对象,需要new
内置对象:
由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现,即在引擎初始化阶段就被创建好的对象。这意味着开发者不必明确实例化内置对象,它已被实例化了
同样是“独立于宿主环境”。根据定义我们似乎很难分清“内置对象”与“本地对象”的区别。而ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每个内置对象都是本地对象)。如此就可以理解了。内置对象是本地对象的一种。
宿主对象:
何为“宿主对象”?主要在这个“宿主”的概念上,ECMAScript中的“宿主”当然就是我们网页的运行环境,即“操作系统”和“浏览器”。
所有非本地对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境提供的对象,包含两大类,一个是宿主提供,一个是自定义类对象,ECMAScript官方未定义的对象都属于宿主对象,所有非本地对象都是宿主对象。宿主提供对象原理—>由宿主框架通过某种机制注册到ECscript引擎中的对象,如宿主浏览器(以远景为参考)会向ECscript注入window对象,构建其实现javascript。所有的BOM和DOM都是宿主对象。说白了就是,ECMAScript官方未定义的对象都属于宿主对象,因为其未定义的对象大多数是自己通过ECMAScript程序创建的对象。