javascripe高级
**面向对象:**面向对象是一种思想,是解决问题的一种思路,即把一个大型的项目分解为对象进行完成,当把分解的每个对象完成后,进行串接,整个项目也就完成了。针对的是对象,针对对象进行问题处理
- JavaScript的组成:
ECMAscript组成:变量、数据类型、类型转换、操作符
流程控制、判断、循环语句
数组、函数、作用域、预解析
对象、属性、方法、简单数据类型、复杂数据类型、以及他们的区别
内置对象:Math、Date、Array、基本包装类型String、Number、布尔
DOM:onload页面加载事件、window顶级对象
定时器
location、history
BOM:获取页面元素、注册事件
属性操作、样式操作
节点操作、节点层级
动态创建元素
事件:注册事件的方式、事件的三个阶段、事件对象
JavaScript的执行过程:
全局预解析:变量和函数声明会被提升,变量和函数同名,函数的有优先级最高
函数内部预解析(函数、形参、普通变量都会参与预解析)
- 面向对象的设计思想:
1、抽象出构造函数(Class)
2、根据构造函数创建对象实例(Instance)
3、指挥Instance得出结果
- 创建对象:
1、通过new Obiect()创建对象
var person = new Object()
person.name = 'Jack'
person.age = 18
person.sayName = function () {
console.log(this.name)
}
2、通过对象字面量创建对象
var person = {
name: 'Jack',
age: 18,
sayName: function () {
console.log(this.name)
}
}
3
、通过上面的方法,如果要生成多个对象,需要多次创建,显得代码太过冗余,此时我们可以使用工厂函数进行对象实例化
function createPerson (name, age) {
return {
name: name,
age: age,
sayName: function () {
console.log(this.name)
}
}
}
4、虽然使用工厂函数解决了代码冗余的问题,但是还是无法解决对象识别的问题,此时需要使用构造函数来完成解决我们的问题
function Person (name, age) {
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
}
var p1 = new Person('Jack', 18)
p1.sayName() // => Jack
var p2 = new Person('Mike', 23)
p2.sayName() // => Mike
- 构造函数解析:
1、会在内存中创建一个空对象
2、设置构造函数的this,让this指向刚刚new创建的对象
3、执行构造函数中的代码
4、返回对象
- constructor:构造器
在每个实例对象中同时都有一个constructor属性,该属性指向创建该实例的构造函数
instanceof操作符:判断 检测对象的类型
- 实例对象/静态成员
实例对象:和对象相关的成员,将来使用对象的方式调用
静态成员:直接构造函数添加的成员,将来调用要使用构造函数来调用
- 原型:
由于对象中我们多次调用可能有时会存在属性和方法重复的问题,这样就会造成内存浪费,针对这个问题,我们提出了原型的概念,因为原型在每个对象中都是公共存在的,我们只需要把相同的属性和方法拿出来放到公共存在的原型中,让大家用到的时候都可以去里面调用,这样就大大的节约了空间,减少了内存浪费。
每一个构造函数都有一个属性:原型/原型对象
prototype:构造函数的原型对象,是一个对象。
function Student (name, age) {
this.name = name
this.age = age
}
Student .prototype.type = 'human'
Student .prototype.sayName = function () {
console.log(this.name)
}
我们通过Student构造函数创建的对象,可以访问Student.prototype中的成员
即此时type属性和sayName()方法都是prototype对象
var s1 = new Student(‘lilei’, 18, ‘男’);
var s2 = new Student(‘hmm’, 18, ‘女’);
当我们调用对象的属性和方法的时候,先去找对象本身的属性和方法,如果对象本身没有此属性和方法,再去调用原型对象中的属性和方法。如果对象本身没有此属性和方法,原型对象中也没有此属性和方法,此时就会报错
s1.proto 就等价于Student.prototype 都是原型对象
在原型对象prototype中有都一个属性constructor 构造函数 指向构造函数本身
constructor记录创建该对象的构造函数,记录了创建该对象的构造函数
s1.constructor===Student
构造函数 实例 原型 三者之间的关系
实例是通过构造函数new出来的实例对象 — 实例对象的__proto__的原型对象就是构造函数的prototype,他们之间呈现三角关系
通过构造函数得到的实例对象内部都会包含一个 指向构造函数的prototype对象的指针__proto__
所有实例对象都直接或者间接继承了原型对象的成员
- 原型链的概念:
就是实例对象的原型的原型,就一直往上原型,形成的一个链子,当找到最后没有找到就会返回undefined
属性设置一般情况设置在构造函数的本身
方法设置一般情况设置在构造函数的原型对象中
当我们重新改变原型对象prototype的时候,需要重新设置constructor属性。因为重新改变原型对象,相当于赋值,会把之前对象中的属性都会清空覆盖,此时就需要重新设置给constructor属性
正确设置原型属性
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person, // => 手动将 constructor 指向正确的构造函数
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}
所有函数都有prototype属性对象,但是数组或者String中的prototype是不可修改的
贪吃蛇案例:旨在体会面向对象思想
我们创建三个对象进行
食物对象
蛇对象
游戏对象
每个对象中又对应不同的属性和方法
在开发过程中会涉及到自调用函数:目的是新开辟一个新的作用域,防止命名冲突
有时可能出现报错,是由于自调用函数之间没有写 ; 导致的,解决办法 在自调用函数前面加上 ; 即可
如果使用了自调用函数,在函数的最后需要把外部用到的构造函数暴露出去,通过window暴露构造函数给外部,自调用函数的参数window和undefined,传入window的目的,是让变量名可以被压缩;传入undefined的目的,可以被重新赋值
**bind()方法:**bind可以新建一个方法,bind中的第一个参数可以改变函数中的this指向
bind可以返回一个新的函数 没有调用方法, 需要用变量接收,然后在调用
**call()**改变函数中的this直接调用函数
- 继承:
构造函数的属性继承:借用构造函数:利用call()方法,让子类型借用父类型的构造函数
function Person (name, age) {
this.type = 'human'
this.name = name
this.age = age
}
function Student (name, age) {
// 借用构造函数继承属性成员
Person.call(this, name, age)
}
var s1 = Student('张三', 18)
console.log(s1.type, s1.name, s1.age) // => human 张三 18
构造函数的原型方法继承:拷贝继承
function Person (name, age) {
this.type = 'human'
this.name = name
this.age = age
}
Person.prototype.sayName = function () {
console.log('hello ' + this.name)
}
function Student (name, age) {
Person.call(this, name, age)
}
// 原型对象拷贝继承原型对象成员
for(var key in Person.prototype) {
Student.prototype[key] = Person.prototype[key]
}
var s1 = Student('张三', 18)
s1.sayName() // => hello 张三
原型继承:
function Person (name, age) {
this.type = 'human'
this.name = name
this.age = age
}
Person.prototype.sayName = function () {
console.log('hello ' + this.name)
}
function Student (name, age) {
Person.call(this, name, age)
}
// 利用原型的特性实现继承
Student.prototype = new Person()
var s1 = Student('张三', 18)
console.log(s1.type) // => human
s1.sayName() // => hello 张三
继承目的:把子类型中共同的成员提取到父类型中
原型继承:无法设置构造函数中的参数
组合继承:借用构造函数 + 原型继承
通过借用构造函数让子类型继承父类型的属性 通过原型继承的方式让子类型继承父类型的方法实现组合继承
函数 : 函数也是对象 new Function()
函数声明:
function foo () {
}
函数表达式:
var foo = function () {
}
函数声明和函数表达式的区别:
1、函数声明必须 有名字
2、函数声明会函数提升,在预解析阶段就已经创建,声明前后都可以调用
3、函数表达式类似于变量赋值
4、函数表达式可以没有名字 例如匿名函数
5、函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用
函数的调用方式: this的指向
普通函数调用 – this指向是window
方法调用 – this指向是调用该方法的对象
构造函数调用 – 由该构造函数创建的实例对象
作为事件处理函数 – 触发该事件的对象
定时器的函数 – this指向是window
总之,谁调用指向谁
call bind apply方法总结 :如果第一个参数指定了null或undefined,内部this指向window
call:
1、自身可以调用函数,改变函数中this的指向
2、第一个参数,设置函数内部this的指向,其他参数,对应函数的参数
3、call的返回值就是函数的返回值
apply:
1、自身可以调用函数,改变函数中this的指向
2、只有两个参数,参数1设置函数内部this的指向,参数2是数组
3、apply的返回值就是函数的返回值
bind:
1、改变函数中的this,不会调用函数,而是把函数赋值一份
2、第一个参数,设置函数内部this的指向,其他参数,对应函数的参数
3、bind的返回值就是函数的返回值
函数的其他成员:
arguments — 实参集合
caller — 函数的调用者
length — 形参的个数
name — 函数的名字
- 高阶函数
什么是高阶函数:
1、函数可以作为参数
function eat (callback) {
setTimeout(function () {
console.log('吃完了')
callback()
}, 1000)
}
eat(function () {
console.log('去唱歌')
})
2、函数可以作为返回值
function genFun (type) {
return function (obj) {
return Object.prototype.toString.call(obj) === type
}
}
var isArray = genFun('[object Array]')
var isObject = genFun('[object Object]')
console.log(isArray([])) // => true
console.log(isArray({})) // => true
- 函数闭包:
作用域有 全局作用域 函数作用域 没有快捷作用域 内层作用域可以访问外层作用域,反之不行
什么是闭包
闭包就是可以读取其他函数内部变量的函数(在一个作用域中可以访问另一个作用域中的变量) 延展了函数的作用域范围
闭包的用途:
可以在函数外部读取 函数内部成员
让函数内部成员始终存活在内存中
- 递归:
函数自己调用函数
一般都要写结束条件
掌握递归思想
**深拷贝:**将对象中的属性和方法全部拷贝,且对新拷贝的对象操作不会对被拷贝的对象有任何影响
**浅拷贝:**只能拷贝对象中的一级属性和方法,如果内部再有新的对象将不能进行拷贝,对新拷贝的对象操作会影响被拷贝对象中的二级属性。
-
递归思想遍历:
1、 // 遍历指定元素下所有的子元素
function loadTree(parent, callback) {
for (var i = 0; i < parent.children.length; i++) {
// 遍历第一级子元素
var child = parent.children[i];
// console.log(child);
if (callback) {
// 处理找到的子元素
callback(child);
}// 递归调用
loadTree(child);
}
}
2、 // 深度优先
function loadTree(parentElement) {
var childElements = parentElement.children;
for (var i = 0; i < childElements.length; i++) {
var childElement = childElements[i];
// 需要执行的操作
console.log(childElement);
// 输出子元素下的子元素节点
loadTree(childElement);
}
}
loadTree(document.body);
3、 // 广度优先
var elementList = [];
// 数组操作的4个方法 push pop unshift shift
// 假设有一个方法,从数组中取出元素,进行打印,再进入这个元素继续打印下面所有的元素
function printElement() {
// 如果数组中有元素,就继续执行
while (elementList.length > 0) {
var element = elementList.shift();
// 执行的操作
console.log(element);
var childElements = element.children;
for (var i = 0; i < childElements.length; i++) {
var childElement = childElements[i];
elementList.push(childElement);
}
}
}
elementList.push(document.body);
printElement();
相比较而言,广度优先性能最优
- 正则表达式
就是通过一些规则制定功能,便于我们开发
正则表达式组成:普通字符 特殊字符(元字符)
常用元字符串:
\d — 匹配数字
\D — 匹配任意非数字的字符
\w — 匹配字母或数字或下划线
\W — 匹配任意不是字母 数字 下划线
\s — 匹配任意空白符
\S — 匹配任意不是空白符的字符
. — 匹配除换行符以外的任意单个字符
^ — 匹配行首的文本(以谁开始)
$ — 匹配行尾的文本(以谁结束)
限定符:
- — 重复零次或者更多次
- — 重复一次或者更多次
? — 重复零次或者一次
{n} — 重复n次
{n,}— 重复n 次或者更多次
{n,m}— 重复n到m次
其它符号
[] – 字符串用中括号括起来,表示匹配其中任一字符,相当于或的意思
[^] – 匹配除中括号内的以外的内容
\ – 转义符
| – 选择两者的一个,注意 | 将内容分为两部分,而不管左右两边有多长多乱
()-- 从两个直接量中选择一个,分组eg:gr(a|e)y匹配gray和grey
[\u4e00-u9fa5] – 匹配汉字
案例:
验证手机号:^\d{11}$ — 11位数字开始 11位数字结束
验证邮编: ^\d{6}$ — 6位数字开始 6位数字结束
验证日期:^\d({4}-\d{1,2}-\d{1,2}$ 4位数字开始 中间两位 后面两位
验证邮箱:^\w+@\w+.\w$ 以字母或者数字开始 以字母或者数字结束
验证IP地址:^\d{1,3}(.\d{1,3}){3}$ 3位数字