前端大全(基础总结)(根据js权威指南扩展)
javascript 权威指南第六版
// 提出问题 + 实例 + 练习
第一部分
基础知识
用;来分隔开。如果一条语句单独占一行,可以不用;
数据类型
- (基本数据类型)原始数据类型:数字Number,字符串String,布尔值Boolean,null ,undefined symbol symbol(数据)返回一个唯一的symbol数据类型的值。symbol(‘foo’) !== symbol(‘foo’)可以用来当key值。
- (引用数据类型)对象类型:键值对。
- 特殊对象:全局对象,JavaScript代码中,Window对象充当了全局对象。
- 数组
- 函数:调用函数执行可执行代码,返回运算结果
js变量无类型。变量可以被赋予任何类型的值,
js不区分整型浮点型数值,所有的数组都是浮点类型。整型直接量:es6支持十六、十进制,禁止八进制。
正数溢出结果:infinity
负数溢出结果:0
大整数:又叫高精度数,在这之前你可能更愿意使用大整数进行重要的金融计算,例如,要使用整数“分”而不要使用小数“元”进行基于货币
解决浮点数精度问题的计算:
数据转换
- 转换为布尔值(调用Boolean()方法)
- 转换为数字(调用Number()、parseInt()和parseFloat()方法)
- 转换为字符串(调用.toString()或者String()方法)
null和underfined没有.toString方法
!!会把右边的值转换为布尔值
隐式数据类型转换
+
'+'两侧只要有一侧是字符串,另一侧的数字则会自动转换成字符串,因为其中存在隐式转换
// String + String -> concatenation
'foo' + 'bar' // "foobar"
// Number + String -> concatenation
5 + 'foo' // "5foo"
// String + Boolean -> concatenation
'foo' + false // "foofalse"
-
如果有一边是字符,会把字符转成数字。
var a = '11';
a = a - '';
alert(typeof a);// -->number
数据类型的判断
- typeof,
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof []); // object []数组的数据类型在 typeof 中被解释为 object
console.log(typeof function(){}); // function
console.log(typeof {}); // object
console.log(typeof undefined); // undefined
console.log(typeof null); // object null 的数据类型被 typeof 解释为 object
- instanceof,
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
// console.log(undefined instanceof Undefined);
// console.log(null instanceof Null);
- constructor,
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
这里有一个坑,如果我创建一个对象,更改它的原型,constructor就会变得不可靠了
function Fn(){};
Fn.prototype=new Array();
var f=new Fn();
console.log(f.constructor===Fn); // false
console.log(f.constructor===Array); // true
- Object.prototype.toString.call()
var a = Object.prototype.toString;
console.log(a.call(2));// [object Number]
console.log(a.call(true));// [object Boolean]
console.log(a.call('str'));// [object String]
console.log(a.call([]));// [object Array]
console.log(a.call(function(){}));// [object Function]
console.log(a.call({}));// [object Object]
console.log(a.call(undefined));// [object Undefined]
console.log(a.call(null));// [object Null]
a=b=0
实例1
function () {
let a = b = 0
}
为什么 b是一个全局变量??
因为赋值运算是从右往左的。所以该语句实际上是 b = 0 、let a = b
b隐性声明为全局变量。因为声明语句不带关键保留字默认是var声明
js内置对象
全局的对象( global objects )或称标准内置对象,不要和 “全局对象(global object)” 混淆。这里说的全局的对象是说在全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。
标准内置对象的分类
(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。
例如 Infinity、NaN、undefined、null 字面量
(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。
例如 eval()、parseFloat()、parseInt() 等
(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。
例如 Object、Function、Boolean、Symbol、Error 等
(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。
例如 Number、Math、Date
(5)字符串,用来表示和操作字符串的对象。
例如 String、RegExp
(6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array
(7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。
例如 Map、Set、WeakMap、WeakSet
(8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。
例如 SIMD 等
(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。
例如 JSON 等
(10)控制抽象对象
例如 Promise、Generator 等
(11)反射
例如 Reflect、Proxy
(12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。
例如 Intl、Intl.Collator 等
(13)WebAssembly
(14)其他
例如 arguments
{} 的 valueOf 结果为 {} ,toString 的结果为 "[object Object]"
[] 的 valueOf 结果为 [] ,toString 的结果为 ""
当我们对两种类型使用 typeof 进行判断的时候,Null 类型化会返回 “object”,这是一个历史遗留的问题。当我们使用双等 号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
String对象的属性和方法
// endsWith 是否以指定的字符结尾 return true or false
var str = 'ba'
console.log(str.endsWith('a'))// true
// charAt() 返回一个指定的字符.string.chartAt(number)
const charat = str.charAt(0)
console.log(charat)// b
// charCodeAt() 返回utf16
const charcodeat = str.charCodeAt(str.charAt(1))
console.log(str.charAt(1), ':', charcodeat)
// concat 连接多个字符,返回新的数组
str.concat('11', '22', '333', '44')// ba112233344
str.concat(['11', '22', '333', '44'])// ba11,22,333,44
// includes() 区分大小写
// str.includes(target, 开始搜索的位置index) return true or false
var str = 'To be, or not to be, that is the question.';
console.log(str.includes('To be')); // true
console.log(str.includes('question')); // true
console.log(str.includes('nonexistent')); // false
console.log(str.includes('To be', 1)); // false
console.log(str.includes('TO BE')); // false
// indexOf 返回目标子串在字符串中第一次出现的index,没有的返回-1
str = 'babb'
str.indexOf('a', 1)// 1 found return index
str.indexOf('a', 2)// -1 not found return -1
// lastIndexOf() target appears last index
返回数组最后一次出现的位置
// match(regx||字符串) 返回匹配的字符数组return match array
str = 'Baba1BBaa2bbaa3'
const re = /[a-b]/gi
console.log(re)
str.match(re)
/**
\* [
'B', 'a', 'b', 'a',
'B', 'B', 'a', 'a',
'b', 'b', 'a', 'a'
]
*/
// repeat (time) 字符重复的元素,相当于连接字符串本身
str = '123'
str.repeat(0)// ' '
str.repeat(1)// 123
str.repeat(1.5) // 123
// replace
return new string ,do not change origin string string.replace(reg or string, target)
(function () {
const str = 'arra'
var regx = /a/gi
// 使用gi正则和字符的区别.
console.log(str.replace(regx, 'replace'))// replacerrreplace
console.log(str.replace('a', 'replace'))// replacerra
console.log(str)// arr
})()
// slice
return new ,not change origin slice(startIndex[, endIndex])
// string.split () unsuggest.
不建议是用split来分割字符串
console.log('????????????????'.split(''))
/**
\* [
'�', '�', '�',
'�', '�', '�',
'�', '�'
]
*/
// instead:
解构
function tosplit (str) {
const arr = [...str]
console.log(arr)
}
tosplit('????????????????')// [ '????', '????', '????', '????' ]
// or array.from
(
function () {
console.log(Array.from('????????????????'))// [ '????', '????', '????', '????' ]
console.log(Object.assign([], '????????????????'))
/**
\* [
'�', '�', '�',
'�', '�', '�',
'�', '�'
]
*/
}
)()
// substr
substr(start, length) 和slice的区别???
// slice(start,end) substr(start, length)
str = 'substr'
var num = 123
// num.length // undefined!!
console.log(str.substr(0, 3),
str.length,
num.length,// undefined 数字没有length属性
Object.prototype.toString.call(num.toString())
)// sub 6 undefined [object String]
Array
把可遍历的对象或者伪数组变为数组
参数: // array.from(interate or 伪数组 [,回调[,指定回调的this]])
第二个可选: 对每一个遍历元素执行的回调函数。第三个参数指定回调函数的this 例如:
var arr = [1, 2, 3]
var obj = {
name: 'rong'
}
console.log(Array.from([1, 3, 4],
// x => {
// x + this.name 箭头函数没有this
// },
// function call (x) {
// x + this.name
// },
// x => x + x, // 箭头函数有一个隐式返回return,
function call (x) {
return this.name + x
},
obj))
##### json对象
两个作用
1 有用于解析json数据交换格式子集的parse()方法
2 将对象转换成json的stringify()
除此之外没有其他作用,不能被调用和作为构造函数。
date对象
Date() | 返回当日的日期和时间。 |
---|---|
getDate() | 从 Date 对象返回一个月中的某一天 (1 ~ 31)。 |
getDay() | 从 Date 对象返回一周中的某一天 (0 ~ 6)。 |
getMonth() | 从 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 日至今的毫秒数。 |
getTimezoneOffset() | 返回本地时间与格林威治标准时间 (GMT) 的分钟差。 |
getUTCDate() | 根据世界时从 Date 对象返回月中的一天 (1 ~ 31)。 |
getUTCDay() | 根据世界时从 Date 对象返回周中的一天 (0 ~ 6)。 |
getUTCMonth() | 根据世界时从 Date 对象返回月份 (0 ~ 11)。 |
getUTCFullYear() | 根据世界时从 Date 对象返回四位数的年份。 |
getUTCHours() | 根据世界时返回 Date 对象的小时 (0 ~ 23)。 |
getUTCMinutes() | 根据世界时返回 Date 对象的分钟 (0 ~ 59)。 |
getUTCSeconds() | 根据世界时返回 Date 对象的秒钟 (0 ~ 59)。 |
getUTCMilliseconds() | 根据世界时返回 Date 对象的毫秒(0 ~ 999)。 |
parse() | 返回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 对象。 |
setUTCDate() | 根据世界时设置 Date 对象中月份的一天 (1 ~ 31)。 |
setUTCMonth() | 根据世界时设置 Date 对象中的月份 (0 ~ 11)。 |
setUTCFullYear() | 根据世界时设置 Date 对象中的年份(四位数字)。 |
setUTCHours() | 根据世界时设置 Date 对象中的小时 (0 ~ 23)。 |
setUTCMinutes() | 根据世界时设置 Date 对象中的分钟 (0 ~ 59)。 |
setUTCSeconds() | 根据世界时设置 Date 对象中的秒钟 (0 ~ 59)。 |
setUTCMilliseconds() | 根据世界时设置 Date 对象中的毫秒 (0 ~ 999)。 |
toSource() | 返回该对象的源代码。 |
toString() | 把 Date 对象转换为字符串。String(false)==false.toString() =>"false" |
toTimeString() | 把 Date 对象的时间部分转换为字符串。 |
toDateString() | 把 Date 对象的日期部分转换为字符串。 |
toGMTString() | 请使用 toUTCString() 方法代替。 |
toUTCString() | 根据世界时,把 Date 对象转换为字符串。 |
toLocaleString() | 根据本地时间格式,把 Date 对象转换为字符串。 |
toLocaleTimeString() | 根据本地时间格式,把 Date 对象的时间部分转换为字符串。 |
toLocaleDateString() | 根据本地时间格式,把 Date 对象的日期部分转换为字符串。 |
UTC() | 根据世界时返回 1970 年 1 月 1 日 到指定日期的毫秒数。 |
valueOf() | 返回 Date 对象的原始值。 |
RgeExp正则表达式
RgeExp是一种强大和常用的文本处理工具
用\d
可以匹配一个数字,\w
可以匹配一个字母或数字
\s
可以匹配一个空格(也包括Tab等空白符),所以\s+
toString表示至少有一个空格
\d{3,8}
表示3-8个数字
RegExp对象的test()
方法用于测试给定的字符串是否符合条件。
var re = /^\d{3}\-\d{3,8}$/;
re.test('010-12345'); // true
re.test('010-1234x'); // false
re.test('010 12345'); // false
要匹配变长的字符,在正则表达式中,用*
表示任意个字符(包括0个),用+
表示至少一个字符,用?
表示0个或1个字符,用{n}
表示n个字符,用{n,m}
表示n-m个字符:
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()
表示的就是要提取的分组(Group)
var re4 = /\d{3}\-\d{0,9}/
// qq邮箱匹配
var re5 = /[1-9][\d{4,}][email protected]/
// 手机号匹配
var re6 =/1[3-8][0-9]{9}/
var re7 =/^1[3-8][0-9]{9}$/
console.log(re4.test('111-349573'))//true
console.log(re5.test('[email protected]'))//true
console.log(re6.test('13302339263'))//true
console.log(re6.test('133023392sdfa'))//false
console.log(re6.test('133023392632'))//true
console.log(re7.test('133023392632'))//false
var test = /^[\w]+\@[\w]+\.(com|org)$/ //或不是用[com|org]而是用(com|org)
console.log('test'+test.test('[email protected]'))//false
null
对null执行typeof预算,结果返回字符串“object”,也就是说,可以将null认为是一个特殊的对象值,含义是“非对象”
undefined
undefined是预定义的全局变量(它和null不一样,它不是关键字),它的值就是“未定义”。用typeof运算符得到undefined的类型,则返回“undefined“
全局对象
当JavaScript解释器启动时(或者任何Web浏览器加载新页面的时候),它将创建一个新的全局对象,并给它一组定义的初始属性:
·全局属性,比如undefined
、Infinity
和NaN
·全局函数,比如isNaN()
、parseInt()
(见3.8.2节)和eval()
(见4.12节)
parsInt()
parseInt()只解析整数,而parseFloat()则可以解析整数和浮点数。如果字符串前缀是“0x”或者“0X”, parseInt()将其解释为十六进制数,parseInt()和parseFloat()都会跳过任意数量的前导空格,尽可能 解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接量,将最终返回NaN: parseInt()可以接收第二个可选参数,这个参数指定数字转换的基数,合法的取值范围是2~36
·构造函数,比如Date()
、RegExp()
、String()
、Object()
和Array()
(见3.8.2节)
·全局对象,比如Math和JSON(见6.9节)
Window
JavaScript代码中,Window对象充当了全局对象。这个全局Window对象有一个属性window引用其自身,它可以代替this
来引用全局对象。JavaScript全局变量是全局对象的属性
window
对象不但充当全局作用域,而且表示浏览器窗口。
window
对象有innerWidth
和innerHeight
属性,可以获取浏览器窗口的内部宽度和高度。
console.log('window inner size: ' + window.innerWidth + ' x ' + window.innerHeight);
变量作用域
作用域是什么????
全局变量拥有全局作用域,在JavaScript代码中的任何地方都是有定义的。然而在函数内声明的变量只在函数体内有定义。它们是局部变量,作用域是局部性的。函数参数也是局部变量,它们只在函数体内有定义。而JavaScript中没有块级作用域。JavaScript取而代之地使用了函数作用域(function scope):变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
作用域是在什么时候定义的
作用域链
我们将作用域链描述为一个对象链表,不是绑定的栈。每次调用JavaScript函数的时候,都会为之创建一个新的对象用来保存局部变量,把这个对象添加至作用域链中。当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套的函数,也没有其他引用指向这个绑定对象,它就会被当做垃圾回收掉
作用域链由一个全局对象组成。在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。理解对象链的创建规则是非常重要的。
当定义一个函数时,它实际上保存一个作用域链。当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。
对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。内部函数在每次定义的时候都有微妙的差别——在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。
实例1
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn() //输出?
// print 2
实例2
var a = 1
function fn1(){
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn() //输出多少
// print 1
实例3
var a = 1
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
var a
fn2()
a = 4
}
var a = 2
return fn3
}
var fn = fn1()
fn() //输出多少
// print undefined
练习1
var x = 10
bar()
function foo() {
console.log(x)
}
function bar(){
var x = 30
foo()
}
// print 10
/*
第2行,bar()调用bar函数
第6行,bar函数里面调用foo函数
第3行,foo函数从自己的局部环境里找x,结果没找到
第1行,foo函数从上一级环境里找x,即从全局环境里找x,找到了var x=10。
foo()的输出结果为10。
*/
练习2
var x = 10;
bar() //30
function bar(){
var x = 30;
function foo(){
console.log(x)
}
foo();
}
// print 30
练习3
var x = 10;
bar()
function bar(){
var x = 30;
(function (){
console.log(x)
})()
}
// print 30
new
new的过程
第六章对象
创建对象的几种方式
工厂模式
创建多个相似的对象,解决代码复用,重复简单相似的操作。
工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。
但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。
function CreatePerson(name,age,sex) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.sayName = function(){
return this.name;
}
return obj;
}
var p1 = new CreatePerson("longen",'28','男');
var p2 = new CreatePerson("tugenhua",'27','女');
console.log(p1.name); // longen
构造函数模式
第二种是构造函数模式。js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么我们就可以把它称为构造函数。执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的 prototype 属性,然后将执行上下文中的 this 指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因为 this 的值指向了新建的对象,因此我们可以使用 this 给对象赋值。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型。
但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次我们都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。
function createPerson(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
return this;
}
var person1 = new createPerson("james",9,"student");
person1.sayName()
person1.age // 9
原型模式
原理
创建的每一个函数都有一个 prototype 属性,这个属性指向函数的原型对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
我们通过使用原型对象可以让所有的对象实例共享它所包含的属性和方法,因此这样也解决了代码的复用问题
优点:解决了构造函数模式中多次创建相同函数对象的问题,所有的实例可以共享同一组属性和函数。
缺点: 但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。
function Person(){
}
Person.prototype.name = "james";
Person.prototype.age = 9;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
person1.sayName(); // "james"
var person2 = new Person();
person2.sayName(); // "james"
console.log(person1.sayName === person2.sayName) // true
原型和构造函数组合模式
合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。
function Person(name, age, job){
// 创建的每一个新的类都有不同的属性值
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
// 通过原型可以写重复的方法,从而避免构造函数中重复定义一个新的方法
constructor: Person,
sayName: function(){
alert(this.name);
}
}
var person1 = new Person("james", 9, "student");
console.log(person1.name); // "james"
console.log(person2.name); // "kobe"
// 通过原型可以写重复的方法,从而避免构造函数中重复定义一个新的方法
console.log(person1.sayName === person2.sayName); // true
缺点:
存在封装的问题
动态原型模式
把原型封装在构造函数之间,在构造函数之间通过if判断来只初始化一次原型
function Person (name, age, job) {
this.name = name
this.job = job
this.age = age
if (typeof this.sayName !== 'function') {
Person.prototype.sayName = function () {
alert(this.name )
}
}
}
var person1 = new Person('aa', 18, 'student')
person1.sayName()
注意在 if 语句中检查的可以是初始化后应该存在的任何属性或方法,不必要检查每一个方法和属性,只需要检查一个就行。
优点:解决了混成模式中封装性的问题
寄生构造函数模式
稳妥构造函数模式
原型
js使用构造函数来建立对象每一个JavaScript对象(null除外)都和另一个对象相关联。“另一个”对象就是我们熟知的原型,每一个对象都从原型继承属性。可以通过JavaScript代码Object.prototype
获得对原型对象的引用。通过关键字new和构造函数调用创建的对象的原型就是构造函数的prototype
属性的值。同使用{}创建对象一样,通过new Object()创建的对象也继承自Object.prototype
。同样,通过new Array()创建的对象的原型就是Array.prototype
,通过new Date()创建的对象的原型就是Date.prototype
函数对象的prototype属性
创建的每一个函数都有prototype属性,这个属性是一个指针指向一个对象,这个对象包含所有实例共享的属性和方法。
实例1
function Person(){
}
// 为原型对象添加方法
Person.prototype.sayName = function(){
alert(this.name);
}
constructor 属性
当函数创建,prototype 属性指向一个原型对象时,在默认情况下,这个原型对象将会获得一个 constructor 属性,这个属性是一个指针,指向 prototype 所在的函数对象。
实例1
function Person(){
}
// 为原型对象添加方法
Person.prototype.sayName = function(){
alert(this.name);
}
Person.prototype.constructor指向Person对象
对象的__proto__
属性
当我们调用构造函数创建一个新实例后,在这个实例的内部将包含一个指针
__proto__
,指向构造函数的原型对象
var student = new Person();
console.log(student.__proto__ === Person.prototype);// true
即实例对象的__proto__
属性是一个指针,指向构造函数的原型对象即prototype属性指向的对象
Object.create()
ECMAScript 5定义了一个名为Object.create()的方法,它创建一个新对象,其中第一个参数是这个对象的原型。Object.create()提供第二个可选参数,用以对对象的属性进行进一步描述。
var obj = Object.create({x: 1, y: 2})// obj 继承了属性x和y
var Person = {sayName: function(){alert('james')}}
var obj2 = Object.create(Person)// 继承了sayName方法
obj2.sayName()
构造函数对象 构造函数对象原型 实例对象三者的关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DI34ZSpc-1603615831673)(D:\学习笔记\studynote\image\peitu18-5.png)]
原型链
对象的原型链
创建一个Array
对象:
var arr = [1, 2, 3];
其原型链是:
arr ----> Array.prototype ----> Object.prototype ----> null
实例1
function Super(){
};
function Middle(){
};
function Sub(){
};
Middle.prototype = new Super();
Sub.prototype = new Middle();
var suber = new Sub();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rVJSyh0K-1603615831682)(D:\学习笔记\studynote\image\peitu18-7.png)]
函数的原型链
实例1
函数的原型对象
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
function Bar(who) {
Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );
b1.speak();
b2.speak();
prototype与__proto__
原型链的作用
继承
原型链继承
构造函数
组合继承
原型式继承
寄生组合继承
(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。
(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。
(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。
(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。
(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。
(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
selector语法
略
第七章数组
数组原生方法
略
第八章函数
创建函数
怎么创建一个函数?
函数调用
- 作为函数
function foo () {
}
foo()
-
作为构造函数
如果函数或者方法调用之前带有关键字new,它就构成构造函数调用
????
- 作为方法(对象的方法)
var obj = {
func: function () {
return 1
}
}
obj.func
// ƒ () {return 1}
obj.func() // print 1
- 通过call() 和apply()
闭包closure
闭包就是能够读取其他函数内部变量的函数
实例1
function a(){
var n = 0;
function add(){
n++;// 此处的n++和++n没有什么区别
console.log(n);
}
return add;
}
var a1 = a(); //注意,函数名只是一个标识(指向函数的指针),而()才是执行函数;
a1(); //1
a1(); //2
实例2
function waitSomeTime(msg, time) {
setTimeout(function () {
console.log(msg)
}, time);
}
waitSomeTime('hello', 1000);
定时器中有一个匿名函数,该匿名函数就有涵盖waitSomeTime函数作用域的闭包,
因此当1秒之后,该匿名函数能输出msg。
实例3
var fnArr = [];
for (var i = 0; i < 10; i++) {
fnArr[i] = function(){
return i
};
}
console.log( fnArr[3]() ) // 10
实例4
让 i 在每次迭代的时候都产生一个私有作用域,在这个私有的作用域中保存当前i的值
var fnArr = [];
for (var i = 0; i < 10; i++) {
fnArr[i] = (function(){
var j = i
return function(){
return j
}
})()
}
console.log(fnArr[3]()) //3
实例5
将每次迭代的 i
作为实参传递给自执行函数,自执行函数用变量去接收输出值
var fnArr = []
for (var i = 0; i < 10; i ++) {
fnArr[i] = (function(j){
return function(){
return j
}
})(i)
}
console.log( fnArr[3]() ) // 3
this
this在函数体内部,指代函数当前的运行环境。
全局环境中的this
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hAhVXLnB-1603615831684)(D:\学习笔记\studynote\image\16dd77cc2ea643a2.png)]
浏览器中 this
等价于window对象, 用var 声明一个全局变量,可以用this和window访问
this === window
// true
var name = 'name'
this.name
// "name"
// 这是在全局的环境中
函数、方法里面的this
总结 : 运行时this永远指向最后调用它(它指使用this的函数)的那个对象
实例1
var name = "windowsName";
function sayName() {
var name = "Jake";
console.log(this.name); // windowsName
console.log(this); // Window
}
sayName();
console.log(this) // Window
sayName 在全全局中调用,最后的调用sayName的是window, 所以函数里面的this指向调用sayName 的window
实例2
function foo() {
console.log( this.age );
}
var obj1 = {
age : 23,
foo: foo
};
var obj2 = {
age : 18,
obj1: obj1
};
obj2.obj1.foo();
// 23
// 最后调用foo() 的对象是obj1 所以在函数里的this指向的是obj1这个对象。
实例3
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
实例4
var f = function () {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 单独执行
f() // 1
// obj 环境执行
obj.f() // 2
构造函数中的this
构造函数: 就是通过这个函数生成一个新对象(object)。当一个函数作为构造器使用时(通过 new 关键字), 它的 this 值绑定到新创建的那个对象。如果没使用 new 关键字, 那么他就只是一个普通的函数, this 将指向 window 对象。
总结: 谁被new了 this就指向谁.
new 的过程
- 一个新的对象会被创建
- 这个新创建的对象会被接入原型链
- 这个新创建的对象会被设置为函数调用的this绑定
- 除非函数返回一个他自己的其他对象,这个被new调用的函数将自动返回一个新创建的对象
实例1
foo = "bar";
function testThis(){
this.foo = 'foo';
}
console.log(this.foo);// bar
new testThis();
console.log(this.foo);// bar
console.log(new testThis().foo) //foo
call apply bind 中的this
call、apply、bind 被称之为 this 的强绑定
实例1
function dialogue () {
console.log (`I am ${this.heroName}`);
}
const hero = {
heroName: 'Batman',
};
dialogue.call(hero)//明确指定this指向hero对象
箭头函数中的 this
call
语法:
fun.call(thisArg[, arg1[, arg2[, …]]])
与apply的区别就是第二个参数类型不同
调用 call 的对象,必须是个函数 Function
**call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。**如果不传,则默认为全局对象 window。
第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上。但是如果将所有的参数作为数组传入,它们会作为一个整体映射到 Function 对应的第一个参数上,之后参数都为空。
apply
apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数
语法:
fun.apply(thisArg, [argsArray])
thisArg:在 fun 函数运行时指定的 this 值。指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。
实例1
将类数组转换为数据
const arrayLike = {
0: 'qianlong',
1: 'ziqi',
2: 'qianduan',
length: 3
}
const arr = Array.prototype.slice.call(arrayLike);
// print ["qianlong", "ziqi", "qianduan"]
实例2
求数组中的最大值
var arr = [34,5,3,6,54,6,-67,5,7,6,-8,687];
Math.max.apply(Math, arr);
Math.max.call(Math, 34,5,3,6,54,6,-67,5,7,6,-8,687);
Math.min.apply(Math, arr);
Math.min.call(Math, 34,5,3,6,54,6,-67,5,7,6,-8,687);
bind
bind 和 call/apply 用处是一样的,但是
bind
会**返回一个新函数!不会立即执行!**而call/apply
改变函数的 this 并且立即执行。
实例1
原理是闭包, bind返回的是一个函数的拷贝
for (var i = 1; i <= 5; i++) {
// 缓存参数
setTimeout(function (i) {
console.log('bind', i) // 依次输出:1 2 3 4 5
}.bind(null, i), i * 1000);
}
// print 1 2 3 4 5
手写实现??
arguments对象
arguments是什么?
- 是函数中传递的参数值的集合(对象)。箭头函数中没有这个arguments对象
解决:
const four = (...args) => {
args
}
- 是一个类数组对象,因为其中有一个length属性。
所以可以用数组的下标来获取单个值。例如arguments[1]
但是arguments 没有数组的内置方法。
把arguments转换成数组
Array.prototype.slice.call()
第九章类和模块
模块化开发
CommonJS
commonjs模块规范
commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
node是基于commonjs的。实际应用中常使用module.exports 和require导出和应用模块。
实例1
模块math.js
var baseNum = 0
var add = function (x,y) {
return x + y
}
module.exports = {
add,
baseNum
}
引用模块
var math = require('./math.js')
console.log(math.baseNum)
console.log(add(3,5))
// 引用node的核心模块: 语法:
var http = require('http')
http.createServer(() => {
}).listen(3000)
AMD规范
AMD使用异步的方式加载模块,模块的加载不影响它后面语句的运行
所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
依赖的模块会在第一时间加载和执行模块中的代码
使用require.js实现的amd规范
略
cmd规范
AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。
实现cmd规范的sea.js
略
es6 import export
ES6 在语言标准的层面上,实现了模块功能。
其模块功能主要由两个命令构成:
export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。使用
import
命令的时候,用户需要知道所要加载的变量名或函数名。其实ES6还提供了export default
命令,为模块指定默认输出,对应的import
语句不需要使用大括号。这也更趋近于ADM的引用写法。
es6 和commonjs的对比 ????
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第十章正则表达式
略
常用正则表达式:
练习实例题:
第二部分
web浏览器中的js
js的运行机制
单线程
进程是啥?
cpu拥有资源和独立运行的最小单位
线程是啥?
线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程
浏览器是多线程还是单线程
放在浏览器中,每打开一个tab页面,其实就是新开了一个进程,在这个进程中,还有ui渲染线程,js引擎线程,http请求线程等。 所以,浏览器是一个多进程的。
为甚js是单线程
JavaScript语言的一大特点就是单线程,即同一时间只能做一件事情。
js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。 举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变
事件循环
js执行过程中有很多的任务,有些是同步的有些是异步的
主线程是同步执行任务的,事件队列 的任务是异步执行。反过来说,同步执行的放在主线程执行,异步的加入事件队列执行。
时间循环说的是当主线程的任务执行完成为空后,再把事件队列的异步任务读取到主线程执行。这个过程不断循环。
任务可以划分为同步和异步之外还可以细分为宏任务和微任务。
js执行完宏任务之后会立刻执行微任务,宏任务微任务执行完成后再读取事件队列的异步任务到主线程。
实例1
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(resolve, reject) {
console.log(2);
resolve()
}).then(function() {
console.log(3)
});
process.nextTick(function () {
console.log(4)
})
console.log(5)
// print 2 5 4 3 1 错误
// 正确 2 5 4 3 1
process.nextTick指定的异步任务总是发生在所有异步任务之前,因此先执行process.nextTick输出4然后执行then函数输出3
第十五 章DOM
冒泡和捕获事件执行顺序
绑定在被点击元素的事件是按照代码顺序发生,其他元素通过冒泡或者捕获“感知”的事件,按照W3C的标准,先发生捕获事件,后发生冒泡事件。
element.addEventListener(type, function, useCapture) 可以设置事件模型。
element.addEventListener(type, function, useCapture)
//【事件类型】,【事件处理程序】,【可选。布尔值,指定事件是否在捕获或冒泡阶段执行。true:事件句柄在捕获阶段执行;false:默认。事件句柄在冒泡阶段执行】
冒泡是从下向上,DOM元素绑定的事件被触发时,此时该元素为目标元素,目标元素执行后,它的的祖元素绑定的事件会向上顺序执行。
捕获是从上到下的。
addEventListener的第三个参数是false则是冒泡阶段执行,true则是在捕获阶段执行
所有事件的顺序是:其他元素捕获阶段事件 -> 本元素代码顺序事件 -> 其他元素冒泡阶段事件 。
html
<div id='one'>
<div id='two'>
<div id='three'>
<div id='four'>
</div>
</div>
</div>
</div>
实例1
<script type='text/javascript'>
var one=document.getElementById('one');
var two=document.getElementById('two');
var three=document.getElementById('three');
var four=document.getElementById('four');
one.addEventListener('click',function(){
alert('one');
},false);
two.addEventListener('click',function(){
alert('two');
},false);
three.addEventListener('click',function(){
alert('three');
},false);
four.addEventListener('click',function(){
alert('four');
},false);
</script>
都是冒泡事件所以
点击one元素,输出one;
点击two元素,输出two one;
点击three元素,输出 three two one;
点击four元素,输出 four three two one;
实例2
one.addEventListener('click',function(){
alert('one');
},true);
two.addEventListener('click',function(){
alert('two');
},false);
three.addEventListener('click',function(){
alert('three');
},true);
four.addEventListener('click',function(){
alert('four');
},false);
one three 在捕获阶段
four two 在冒泡阶段
点击four 弹出'one three four two'
实例3
one.addEventListener('click',function(){
alert('one');
},true);
two.addEventListener('click',function(){
alert('two,bubble');
},false);
two.addEventListener('click',function(){
alert('two,capture');
},true);
three.addEventListener('click',function(){
alert('three,bubble');
},true);
four.addEventListener('click',function(){
alert('four');
},true);
当一个元素同时绑定了捕获和冒泡事件, 执行顺序是?
点击two 依次弹出: 'one' 'two,capture', 'two, bubble'
事件委托的本质
即一个元素的响应事件( click、keydown)委托到另一个元素
事件委托的实现就是利用事件冒泡
事件冒泡的概念
事件模型的三个阶段
- 1捕获阶段
- 2 目标阶段, 目标元素的响应阶段
- 3 冒泡阶段
委托的优点
减少内存损耗:
实例1
列表,在点击列表项的时候响应一个时间
给每一个列表项绑定一个函数是很耗内存的。可以把该时间绑定到父级元素
jquery的事件委托
实现方法
- $.on: 基本用法:
$('.parent').on('click', 'a', function () { console.log('click event on tag a');
- $.delegate:略
- $.live: 略
实例1
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
// 兼容性处理
var event = e || window.event;
var target = event.target || event.srcElement;
// 判断是否匹配目标元素
if (target.nodeName.toLocaleLowerCase === 'li') {
console.log('the content is: ', target.innerHTML);
}
});
操作DOM
由于HTML文档被浏览器解析后就是一棵DOM树,要改变HTML的结构,就需要通过JavaScript来操作DOM。
始终记住DOM是一个树形结构。操作一个DOM节点实际上就是这么几个操作:
- 更新:更新该DOM节点的内容,相当于更新了该DOM节点表示的HTML的内容;
- 遍历:遍历该DOM节点下的子节点,以便进行进一步操作;
- 添加:在该DOM节点下新增一个子节点,相当于动态增加了一个HTML节点;
- 删除:将该节点从HTML中删除,相当于删掉了该DOM节点的内容以及它包含的所有子节点。
在操作一个DOM节点前,我们需要通过各种方式先拿到这个DOM节点。最常用的方法是document.getElementById()
和document.getElementsByTagName()
,以及CSS选择器document.getElementsByClassName()
由于ID在HTML文档中是唯一的,所以document.getElementById()
可以直接定位唯一的一个DOM节点。document.getElementsByTagName()
和document.getElementsByClassName()
总是返回一组DOM节点。要精确地选择DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。
// 返回ID为'test'的节点:
var test = document.getElementById('test');
// 先定位ID为'test-table'的节点,再返回其内部所有tr节点:
var trs = document.getElementById('test-table').getElementsByTagName('tr');
// 先定位ID为'test-div'的节点,再返回其内部所有class包含red的节点:
var reds = document.getElementById('test-div').getElementsByClassName('red');
// 获取节点test下的所有直属子节点:
var cs = test.children;
// 获取节点test下第一个、最后一个子节点:
var first = test.firstElementChild;
var last = test.lastElementChild;
dom创建
子节点和子元素的区别
节点包括元素和文本节点。元素是节点的一种,元素可以说一种标签,文本节点不包括在元素里。
例如:是个元素,也是一个节点abc不是元素,是文本节点
创建元素节点
实例1 创建节点
var el1 = document.createElement('div');
var el2 = document.createElement('input');
创建文本节点
实例2 创建文本
var node = document.createTextNode('hello world!');
创建注释节点
创建属性节点
var att=document.createAttribute("class");
att.value="democlass";
document.getElementsByTagName("H1")[0].setAttributeNode(att);
dom 查询
querySelector(’#id’ || .className’ || 'tagName ')
实例1
// 返回当前文档中 !!!第一个!!! 类名为 "myclass" 的元素
var el = document.querySelector(".myclass");
// 返回一个文档中!!!!所有的!!!class为"note"或者 "alert"的div元素
var els = document.querySelectorAll("div.note, div.alert");
其他查询
获取父元素、父节点
// 某个元素ele
// 获取父级元素
var parent = ele.parentElement
// 获取父级节点
var parentnode = ele.parentNode
获取子节点
var nodes = ele.children
获取查询指定的子元素(getElementBy…)
1 document.getElementsByClassName() 返回文档中所有指定类名的元素集合,作为 NodeList 对象。
console.log(document.getElementsByClassName('content') instanceof Object)
// true
//获取包含 "example" 和 "color" 类名的所有元素:
var x = document.getElementsByClassName("example color");
// 修改所选中的元素集合中第一个类为 example和 color的样式 x[0]
function myFunction() {
var x = document.getElementsByClassName("example color");
x[0].style.backgroundColor = "red";
}
2 document.getElementById(‘xxx’);返回对拥有指定 id 的第一个对象的引用。
如果没有指定 ID 的元素返回 null
如果存在多个指定 ID 的元素则返回第一个。
console.log(typeof document.getElementById('content') )
// object
3 getElementsByTagName() 方法可返回带有指定标签名的对象的集合。
var els = document.getElementsByTagName('td');
4 document.getElementsByName() 返回带有指定名称的对象集合。
var elson = ele.getElementByTagName('td')
var elson = ele.getElementByClassname('son')
var leson = ele.getElementById('son')
// 获取元素
var el = document.getElementById('xxx');
var els = document.getElementsByClassName('highlight');
// 当前元素的第一个/最后一个子元素节点
var el = ele.firstElementChild;
var el = ele.lastElementChild;
// 下一个/上一个兄弟元素节点
var el = ele.nextElementSibling;
var el = ele.previousElementSibling;
增 删 换 插入子元素
添加子元素
var el = document.createElement('div')
ele.append(el)
删除子元素
ele.remove(el)
替换子元素
ele.replaceChild(el, el2)
插入子元素
parentElement.insertBefore(newElement, referenceElement);
元素属性操作attributes
获取目标元素的{name属性名:value属性值}组成的数组
var attrs = el.attributes
获取、设置某一个元素的属性element.getAttributes(‘属性名key’) 返回key对应的value
获取元素
var tar = document.getElementTagName('div')
// 1 获取目标元素的目标属性
var attr = tar.getAttributes('class')
// 2 设置目标属性的值
el.setAttributes('class', 'heghlight')
// 3 判断是否有属性
el.hasAttributes('class')
// 4 移除属性
el.removeAttributes('class')
DOM 方法(close(),open()…)
DOM属性(domain,title,url…)
DOM事件(onclick,onload,onunload,onchange)
控制台console对象
Console 对象提供console访问浏览器调试模式的信息到控制台。
console.clear 清除控制台
console.clear()
console.error 输出错误
console.error('错误')
// 错误
dom和bom的区别
DOM 指的是文档对象模型,它指的是把文档当做一个对象来对待,这个对象主要定义了处理网页内容的方法和接口
BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM 的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局) 对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 locati on 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对 象的子对象。
即一个是用来操作document文档,一个是用来操作控制浏览器。
dom的作用
操作html中的元素
实例1
document.title = 'title'
当浏览器下载到一个网页,通常是 HTML,这个 HTML 就叫 document(当然,这也是 DOM 树中的一个 node),从上图可以看到,document 通常是整个 DOM 树的根节点。这个 document 包含了标题(document.title)、URL(document.URL)等属性
bom的作用
操作浏览器的api
实例1
window.close()
jQuery
-
如何设置HTML属性、CSS样式和类、HTML表单的值和元素内容、位置高宽,以及数据·如何改变文档结构:对元素进行插入、替换、包装和删除操作
找到所有拥有details类的p元素,将其高亮显示,并将其中隐藏的p元素快速显示出来:
-
如何使用jQuery的跨浏览器事件模型
-
如何用jQuery来实现动画视觉效果
-
jQuery的Ajax工具,如何用脚本来发起HTTP请求
-
jQuery的工具函数
-
jQuery选择器的所有语法,以及如何使用jQuery的高级选择方法
-
如何使用和编写插件来对jQuery进行扩展
-
jQuery UI类库
es6
ECMAscript
ecmascript是什么
ECMAScript 是编写脚本语言的标准,这意味着JavaScript遵循ECMAScript标准中的规范变化,因为它是JavaScript的蓝图。
ECMAScript 和 Javascript,本质上都跟一门语言有关,一个是语言本身的名字,一个是语言的约束条件 只不过发明JavaScript的那个人(Netscape公司),把东西交给了ECMA(European Computer Manufacturers Association),这个人规定一下他的标准,因为当时有java语言了,又想强调这个东西是让ECMA这个人定的规则,所以就这样一个神奇的东西诞生了,这个东西的名称就叫做ECMAScript。
javaScript = ECMAScript + DOM + BOM(自认为是一种广义的JavaScript)
ECMAScript说什么JavaScript就得做什么!
JavaScript(狭义的JavaScript)做什么都要问问ECMAScript我能不能这样干!如果不能我就错了!能我就是对的!
ECMAScript 2015(ES6)有哪些新特性?
块作用域
let var const
**在全局上下文中var声明的变量会挂载在window上,而let和const声明的变量不会:**在函数作用域里都一样。
var a = 100;
console.log(a,window.a); // 100 100
let b = 10;
console.log(b,window.b); // 10 undefined
const c = 1;
console.log(c,window.c); // 1 undefined
复制代码
var声明变量存在变量提升,let和const不存在变量提升:
console.log(a); // undefined ===> a已声明还没赋值,默认得到undefined值
var a = 100;
console.log(b); // 报错:b is not defined ===> 找不到b这个变量
let b = 10;
console.log(c); // 报错:c is not defined ===> 找不到c这个变量
const c = 10;
复制代码
let和const声明形成块作用域
if(1){
var a = 100;
let b = 10;
}
console.log(a); // 100
console.log(b) // 报错:b is not defined ===> 找不到b这个变量
-------------------------------------------------------------
if(1){
var a = 100;
const c = 1;
}
console.log(a); // 100
console.log(c) // 报错:c is not defined ===> 找不到c这个变量
复制代码
同一作用域下let和const不能声明同名变量,而var可以
var a = 100;
console.log(a); // 100
var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;
// 控制台报错:Identifier 'a' has already been declared ===> 标识符a已经被声明了。
复制代码
暂存死区
var a = 100;
if(1){
a = 10;
//在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
// 而这时,还未到声明时候,所以控制台Error:a is not defined
let a = 1;
}
复制代码
const
/*
* 1、一旦声明必须赋值,不能使用null占位。
*
* 2、声明后不能再修改
*
* 3、如果声明的是复合类型数据,可以修改其属性
*
* */
const a = 100;
const list = [];
list[0] = 10;
console.log(list); // [10]
const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj); // {a:10000,name:'apple'}
类
类(class)是在 JS 中编写构造函数的新方法。它是使用构造函数的语法糖,在底层中使用仍然是原型和基于原型的继承。
//ES5 Version
function Person(firstName, lastName, age, address){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
Person.self = function(){
return this;
}
Person.prototype.toString = function(){
return "[object Person]";
}
Person.prototype.getFullName = function (){
return this.firstName + " " + this.lastName;
}
//ES6 Version
class Person {
constructor(firstName, lastName, age, address){
this.lastName = lastName;
this.firstName = firstName;
this.age = age;
this.address = address;
}
static self() {
return this;
}
toString(){
return "[object Person]";
}
getFullName(){
return `${this.firstName} ${this.lastName}`;
}
}
箭头函数
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target
。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
//ES5 Version
var getCurrentDate = function (){
return new Date();
}
//ES6 Version
const getCurrentDate = () => new Date();
function(){}
声明和return关键字,这两个关键字分别是创建函数和返回值所需要的。在箭头函数版本中,我们只需要()括号,不需要 return 语句,因为如果我们只有一个表达式或值需要返回,箭头函数就会有一个隐式的返回。
//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
const greet = (name) => `Hello ${name}`;
const greet2 = name => `Hello ${name}`;
我们还可以在箭头函数中使用与函数表达式和函数声明相同的参数。如果我们在一个箭头函数中有一个参数,则可以省略括号
const data = {
result: 0,
nums: [1, 2, 3, 4, 5],
computeResult() {
// 这里的“this”指的是“data”对象
const addAll = () => {
return this.nums.reduce((total, cur) => total + cur, 0)
};
this.result = addAll();
}
};
// 箭头函数没有自己的this值。它捕获词法作用域函数的this值,在此示例中,addAll函数将复制computeResult 方法中的this值,如果我们在全局作用域声明箭头函数,则this值为 window 对象。
模板字符串
模板字符串是在 JS 中创建字符串的一种新方法。我们可以通过使用反引号使模板字符串化。
//ES5 Version
var greet = 'Hi I\'m Mark';
//ES6 Version
let greet = `Hi I'm Mark`;
复制代码
在 ES5 中我们需要使用一些转义字符来达到多行的效果,在模板字符串不需要这么麻烦:
//ES5 Version
var lastWords = '\n'
+ ' I \n'
+ ' Am \n'
+ 'Iron Man \n';
//ES6 Version
let lastWords = `
I
Am
Iron Man
`;
复制代码
在ES5版本中,我们需要添加\n以在字符串中添加新行。在模板字符串中,我们不需要这样做。
//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
function greet(name) {
return `Hello ${name} !`;
}
复制代码
在 ES5 版本中,如果需要在字符串中添加表达式或值,则需要使用+
运算符。在模板字符串s中,我们可以使用${expr}
嵌入一个表达式,这使其比 ES5 版本更整洁。
对象解构
对象析构是从对象或数组中获取或提取值的一种新的、更简洁的方法。假设有如下的对象:
const employee = {
firstName: "Marko",
lastName: "Polo",
position: "Software Developer",
yearHired: 2017
};
复制代码
从对象获取属性,早期方法是创建一个与对象属性同名的变量。这种方法很麻烦,因为我们要为每个属性创建一个新变量。假设我们有一个大对象,它有很多属性和方法,用这种方法提取属性会很麻烦。
var firstName = employee.firstName;
var lastName = employee.lastName;
var position = employee.position;
var yearHired = employee.yearHired;
复制代码
使用解构方式语法就变得简洁多了:
{ firstName, lastName, position, yearHired } = employee;
复制代码
我们还可以为属性取别名:
let { firstName: fName, lastName: lName, position, yearHired } = employee;
复制代码
当然如果属性值为 undefined 时,我们还可以指定默认值:
let { firstName = "Mark", lastName: lName, position, yearHired } = employee;
实际应用
对象与数组的解析赋值
对象的解构与数组的解析有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
字符串的解构赋值
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
函数参数的解构赋值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]
1.交换变量的值[x, y] = [y, x];
2.从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
console.log(typeof a) //Number
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
3.函数参数的定义
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
4.提取 JSON 数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
上面代码可以快速提取 JSON 数据的值。
5.函数参数的默认值
6.遍历 Map 结构
任何部署了 Iterator 接口的对象,都可以用for…of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
7.输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");
Set
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
我们可以使用Set构造函数创建Set实例。
const set1 = new Set();
const set2 = new Set(["a","b","c","d","d","e"]);
复制代码
我们可以使用add方法向Set实例中添加一个新值,因为add方法返回Set对象,所以我们可以以链式的方式再次使用add。如果一个值已经存在于Set对象中,那么它将不再被添加。
set2.add("f");
set2.add("g").add("h").add("i").add("j").add("k").add("k");
// 后一个“k”不会被添加到set对象中,因为它已经存在了
复制代码
我们可以使用has方法检查Set实例中是否存在特定的值。
set2.has("a") // true
set2.has("z") // true
复制代码
我们可以使用size属性获得Set实例的长度。
set2.size // returns 10
复制代码
可以使用clear方法删除 Set 中的数据。
set2.clear();
复制代码
我们可以使用Set对象来删除数组中重复的元素。
const numbers = [1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 5];
const uniqueNums = [...new Set(numbers)]; // [1,2,3,4,5,6,7,8]
复制代码
另外还有WeakSet
, 与 Set
类似,也是不重复的值的集合。但是 WeakSet
的成员只能是对象,而不能是其他类型的值。WeakSet
中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet
对该对象的引用。
Map
理解
Map 是一个 ES6 中新的用来存储数据的对象之一
他可以添加 删除 修改 查询 读取 遍历自身
let map = new Map([[1, 2], [3, 4]])
在声明 map 时传入的内容有规定,要求必须是嵌套形式的可遍历对象嵌套的数组中第一项代表最终的索引,第二项代表值
方法和属性
.set(index,value)
let a = new Map()
a.set(0,1)
console.log(a)
//输出结果{0=>1}
.set 方法执行方式使他拥有了添加和修改的功能
因为是覆盖式写入如果原索引有值,那么新的值将会修改原来的值这样就完成了修改
Promise模块
为什么:Promise是一个构造函数,解决异步编程回调地狱,
Promise构造函数接收一个函数作为参数,该函数有两个参数resolve,reject,由js引擎提供
状态:padding 进行中 fullfilled已成功 reject已失败。从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
const promise = new Promise(function(resolve, reject){
if (/*异步操作成功条件*/) {
resolve(value)
} else {
reject(Error)
}
})
then 方法 有两个参数,第一个回调参数是Promise对象的状态变为resolve时调用,第二个回调函数是Promise对象状态为reject时调用。第二个回调函数是可选的,这两个回调参数都接收Promise对象传出的值作为参数.then方法返回的是一个新的Promise实例,因此可以采用链式写法,然后再.then()里再调用.then()
catch方法
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数
// 写法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
finally方法
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
all方法
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
参数:数组,数组每个成员都是promise实例
只有等所有的异步都返回了结果才会执行出发then
如果作为参数的 Promise 实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。也就是说rejected是被最近的catch捕获的
all
const p = promise.all([p1,p2,p3])
p的状态
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
如果作为参数的 Promise 实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。如实例1
实例1
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
p1
会resolved
,p2
首先会rejected
,但是p2
有自己的catch
方法,该方法返回的是一个新的 Promise 实例,p2
指向的实际上是这个实例。该实例执行完catch
方法后,也会变成resolved
,导致Promise.all()
方法参数里面的两个实例都会resolved
,因此会调用then
方法指定的回调函数,而不会调用catch
方法指定的回调函数。
promise的执行顺序
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
示例1
异步加载图片
function loadImageAsync (url) {
return new Promise (function(resolve,reject) {
const image = new Image();
image.onload = function () {
resolve(image)
}
image.onerror = function () {
reject(new Error('couldn't load image'))
}
})
imaga.src = url;
}
示例2
封装异步ajax请求
题1
实现多个并发异步请求
Promise面试题
预热
event loop
它的执行顺序:
- 一开始整个脚本作为一个宏任务执行
- 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
- 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
- 执行浏览器UI线程的渲染工作
- 检查是否有
Web Worker
任务,有则执行 - 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空
微任务包括:MutationObserver
、Promise.then()或catch()
、Promise为基础开发的其它技术,比如fetch API
、V8
的垃圾回收过程、Node独有的process.nextTick
。
宏任务包括:script
、setTimeout
、setInterval
、setImmediate
、I/O
、UI rendering
。
注意⚠️:在所有任务开始的时候,由于宏任务中包括了script
,所以浏览器会先执行一个宏任务,在这个过程中你看到的延迟任务(例如setTimeout
)将被放到下一轮宏任务中来执行。
题1
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
})
console.log('1', promise1);
// 'promise1'
// '1' Promise{<pending>}
题2
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve('success')
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
// 1 2 4 3
题3
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
// 1 2 4
题4
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
resolve('resolve1')
})
const promise2 = promise1.then(res => {
console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
// promise1
// Promise {<fulfilled>: "resolve1"}
// Promise {<pending>}
// 'resolve1'
题5
const fn = () => (new Promise((resolve, reject) => {
console.log(1);
resolve('success')
}))
fn().then(res => {
console.log(res)
})
console.log('start')
// 1 start success
题6
const fn = () =>
new Promise((resolve, reject) => {
console.log(1);
resolve("success");
});
console.log("start");
fn().then(res => {
console.log(res);
});
// start 1 success
题7
console.log('start')
setTimeout(() => {
console.log('time')
})
Promise.resolve().then(() => {
console.log('resolve')
})
console.log('end')
// start end resolve time
题8
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = promise1.then(() => {
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
// pedding
// pedding
// fullfilled resolve
// error
题9
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("success");
console.log("timer1");
}, 1000);
console.log("promise1里的内容");
});
const promise2 = promise1.then(() => {
throw new Error("error!!!");
});
console.log("promise1", promise1);
console.log("promise2", promise2);
setTimeout(() => {
console.log("timer2");
console.log("promise1", promise1);
console.log("promise2", promise2);
}, 2000);
// "promise1里的内容"
//"promise1", padding
//"promise2", padding
// timer1
// promise1 success
// promise2 error
'promise1里的内容'
'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
'timer1'
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'timer2'
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}
题10
const promise = new Promise((resolve, reject) => {
reject("error");
resolve("success2");
});
promise
.then(res => {
console.log("then1: ", res);
}).then(res => {
console.log("then2: ", res);
}).catch(err => {
console.log("catch: ", err);
}).then(res => {
console.log("then3: ", res);
})
题11
如何让异步操作顺序执行
Symbol
代理(proxy)
函数默认参数
romise = new Promise((resolve, reject) => {
console.log(1);
resolve(‘success’)
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
// 1 2 4 3
题3
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
// 1 2 4
题4
const promise1 = new Promise((resolve, reject) => {
console.log(‘promise1’)
resolve(‘resolve1’)
})
const promise2 = promise1.then(res => {
console.log(res)
})
console.log(‘1’, promise1);
console.log(‘2’, promise2);
// promise1
// Promise {: “resolve1”}
// Promise {}
// ‘resolve1’
题5
const fn = () => (new Promise((resolve, reject) => {
console.log(1);
resolve(‘success’)
}))
fn().then(res => {
console.log(res)
})
console.log(‘start’)
// 1 start success
题6
const fn = () =>
new Promise((resolve, reject) => {
console.log(1);
resolve(“success”);
});
console.log(“start”);
fn().then(res => {
console.log(res);
});
// start 1 success
题7
console.log(‘start’)
setTimeout(() => {
console.log(‘time’)
})
Promise.resolve().then(() => {
console.log(‘resolve’)
})
console.log(‘end’)
// start end resolve time
题8
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(‘success’)
}, 1000)
})
const promise2 = promise1.then(() => {
throw new Error(‘error!!!’)
})
console.log(‘promise1’, promise1)
console.log(‘promise2’, promise2)
setTimeout(() => {
console.log(‘promise1’, promise1)
console.log(‘promise2’, promise2)
}, 2000)
// pedding
// pedding
// fullfilled resolve
// error
题9
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(“success”);
console.log(“timer1”);
}, 1000);
console.log(“promise1里的内容”);
});
const promise2 = promise1.then(() => {
throw new Error(“error!!!”);
});
console.log(“promise1”, promise1);
console.log(“promise2”, promise2);
setTimeout(() => {
console.log(“timer2”);
console.log(“promise1”, promise1);
console.log(“promise2”, promise2);
}, 2000);
// “promise1里的内容”
//“promise1”, padding
//“promise2”, padding
// timer1
// promise1 success
// promise2 error
‘promise1里的内容’
‘promise1’ Promise{}
‘promise2’ Promise{}
‘timer1’
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
‘timer2’
‘promise1’ Promise{: “success”}
‘promise2’ Promise{: Error: error!!!}
题10
const promise = new Promise((resolve, reject) => {
reject(“error”);
resolve(“success2”);
});
promise
.then(res => {
console.log("then1: ", res);
}).then(res => {
console.log("then2: ", res);
}).catch(err => {
console.log("catch: ", err);
}).then(res => {
console.log("then3: ", res);
})
题11
如何让异步操作顺序执行
#### Symbol
##### 代理(proxy)
##### 函数默认参数
#### rest 和展开
上一篇: 技术问题两则
下一篇: Go 语言 接口(Interface)