原型
每一个构造函数都有一个属性 --> 原型 / 原型对象
function Student(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 为构造函数添加sayHi方法
Student.prototype.sayHi = function () {
console.log("大家好,我是" + this.name);
}
varr stu1 = new Student('a', 19, '男');
var stu2 = new Student('b', 20, '女');
console.log(stu1.sayHi === stu2.sayHi); // true
-
当使用对象的属性或者方法的时候,先去找对象本身的属性/方法,如果对象没有改属性和方法,此时去调用原型中的属性/方法。
-
如果对象本身没有该属性/方法,原型中也没有该属性和方法,此时会报错
-
对象的__ proto __等于构造函数的
Student.prototype
-
__ proto __ 属性是非标准的属性,在真实开发环境中不能去使用
-
在原型对象中有一个属性
constructor
指向构造函数 -
constructor
作用记录了创建该对象的构造函数,记录了创建改对象的构造函数。
构造函数、原型对象、实例/对象 之间的关系
构造函数原型对象 prototype 指向谁?
原型链
属性的查找
-
读取属性
现在对象本身查找属性,如果没有找到的话,回去原型链上查找
-
设置属性
如果对象本身没有这个属性,不会搜索原型链,而是直接在对象本身增加这个属性
注意点
一般情况下,对象的属性在构造函数中来设置,对象的方法在构造函数的原型对象中来设置
function Student(name, age) {
this.name = name;
this.age = age;
}
// 设置方法
Student.prototype.sayHi = function () {
console.log('sayHi');
}
如果方法过多,那么上一种方法中增加方法会不太方便,推荐下一种
Student.prototype = {
sayHi:function () {
//代码
}.
eat: function(){
//代码
}
}
上一种增加构造函数方法虽然简单,但是他的constructor指向就会指向Object,如果想让他指向没有问题,用下面的代码中来解决
Student.prototype = {
constructor:Student,
sayHi:function () {
//代码
}.
eat: function(){
//代码
}
}
所以当我们改变构造函数的prototype的时候,需要重新设置constructor属性
还有一点就是当使用给prototype赋值为一个对象来实现增加方法
的时候,应该先去设置原型属性,在创建对象,才可以访问访问原型对象中的成员
原型对象的应用
扩展内置对象
如果想让内置对象Array想求出所有偶数的和,那么就需要扩展原型对象的方法。
var array = [5, 4, 1, 8];
Array.prototype.getSum = function () {
// 求数组中所有偶数的和
var sum = 0;
for (var i = 0; i < this.length; i++) {
if (this[i] % 2 === 0) {
sum += this[i];
}
}
return sum;
}
console.log(array.getSum());
那么是否可以使用以下的方式进行扩展呢?
Array.prototype = {
getSum: function () {
// 求数组中所有偶数的和
var sum = 0;
for (var i = 0; i < this.length; i++) {
if (this[i] % 2 === 0) {
sum += this[i];
}
}
return sum;
}
}
注意,这种方法时不可取的,因为数组或者String 中的prototype是不可以修改的,即使可以修改,Array原先的prototype已经给我提供了许多方法,这样扩展的话,原先的方法就会丢失
bind方法
改变this的指向
var a = 123;
function fn () {
console.log(this.a); // 123
}
// 利用bind改变指向
var obj = {
a:'abc'
}
var fn1 = fn.bind(obj);
fn1(); // 打印abc
自调用函数的参数
-
自调用函数传入window的目的,是让变量名可以被压缩
-
传入undefined目的是,在老版本的浏览器中,undefined可以被重新复制
;(function (window, undefined) { // 代码 })(window, undefined)
继承
面向对象三大特征:封装、继承、多态(抽象)
JavaScript 不支持多态,可以理解为抽象
对象间的继承(对象的拷贝)
var wjl = {
name: '王健林',
money: 10000000,
cars: ['玛莎拉蒂', '特斯拉'],
houses: ['别墅', '大别墅'],
play: function () {
console.log('打高尔夫');
}
}
var wsc = {
name: '王思聪'
}
// 对象的拷贝
// 复制对象的成员给另一个对象
function extend(parent, child) {
for (var key in parent) {
// 不给wsc复制同名的属性
if (child[key]) {
continue;
}
child[key] = parent[key];
}
}
extend(wjl, wsc);
console.dir(wsc);
原型继承(并不推荐使用)
继承:类型和类型之间的关系
学生类型 老师类型 -> Person类型
继承目的: 把子类型中共同的成员提取到父类型中,代码重用
原型继承:无法设置构造函数的参数
// 父类型
function Person() {
this.name = 'zs';
this.age = 18;
this.sex = '男';
}
// 子类型
function Student() {
this.score = 100;
}
function Teacher() {
this.salary = 3000;
}
// 测试代码
// 继承person
Student.prototype = new Person();
// 改变学生对象的constructor的指向
Student.prototype.constructor = Student;
var s1 = new Student();
console.log(s1.constructor);
console.dir(s1);
call
回顾一下之前的bind(this指向, 形参, 形参)
bind() 改变函数的this,并且返回一个新的函数(不调用函数)
function fn(x, y) {
console.log(this);
console.log(x + y);
}
// window.fn(5, 6);
var o = {
name: 'zs'
};
// bind() 改变函数的this,并且返回一个新的函数 (不调用函数)
var f = fn.bind(o, 1, 2);
f();
call(this指向, 形参, 形参)
call() 改变函数中的this,直接调用函数
fn.call(o, 2, 3);
借用构造函数
继承的另一种方式,缺点是无法继承方法
// 借用构造函数
// 父类型
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
// this.sayHi
}
Person.prototype.sayHi = function () {
console.log(this.name);
}
// 子类型
function Student(name, age, sex, score) {
Person.call(this, name, age, sex);
this.score = score;
}
var s1 = new Student('zs', 18, '男', 100);
console.dir(s1);
组合继承
组合继承:借用构造函数 + 原型继承
// 父类型
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.sayHi = function () {
console.log('大家好,我是' + this.name);
}
// 子类型
function Student(name, age, sex, score) {
// 借用构造函数
Person.call(this, name, age, sex);
this.score = score;
}
// 通过原型,让子类型,继承父类型中的方法
Student.prototype = new Person();
Student.prototype.constructor = Student;
// 学生特有的方法
Student.prototype.exam = function () {
console.log('考试');
}
// var s1 = new Student('zs', 18, '男', 100);
// console.dir(s1);
// var p1 = new Person('ls', 18, '男');
// console.dir(p1);
function Teacher(name, age, sex, salary) {
// 借用构造函数
Person.call(this, name, age, sex);
this.salary = salary;
}
// 通过原型让子类型继承父类型中的方法
Teacher.prototype = new Person();
Teacher.prototype.constructor = Teacher;
var t1 = new Teacher('ww', 30, '男', 100000);
console.dir(t1);
t1.sayHi();
函数进阶
函数的声明方式
-
// 1.函数声明 function fn() { // 代码 } fn();
-
// 2.函数表达式 fn(); // 在这里会报错,注意变量提升 var fn = function () { // 代码 } fn(); // 正常执行
现代浏览器,不会提升if语句中的声明,老版本浏览器会提升,可以利用函数表达式的方式进行定义
-
//3. new Function() var fn = new Function('var name = "haha";console.log(name)'); fn(); console.dir(fn); // 执行速度慢,但是可以认识到函数也是一个对象
函数的调用方式
- 普通函数调用
- this 指向 window
- 方法调用
- this 指向 调用改方法的对象
- 作为构造函数调用
- this 指向由该构造函数创建的对象
- 作为事件的处理函数
- this 触发该事件的对象
- 作为定时器的参数
- this 指向 window
函数内部的this,是由函数调用的时候来确定其指向的
改变函数中的this
-
call()
- 功能: 调用函数,改变函数中的this
- 参数: 第一个参数,设置函数内部this的访问,其他参数为形参
- 返回值:call()返回值就是函数的return的返回值
应用:
// getElementsByTagName() // 伪数组 var obj = { 0: 100, 1: 10, 2: 11, 3: 20, length: 4 }; // 利用Array里面的push方法添加 Array.prototype.push.call(obj, 'a', 'n'); // 利用Array里面的splice方截取 Array.prototype.splice.call(obj, 0, 3);
-
bind()
bind的应用
var obj = { name: 'zs', fun: function() { setInterval(function() { console.log(this.name); }.bind(this), 1000); } } obj.fun(); btn.onclick = function () { // 事件处理函数中的this 是触发该事件的对象 // 通过bind 改变事件处理函数中this的指向
-
apply()
第一个参数为this的指向(一般不需要更改),第二个参数为一个数组
apply的应用
// 2 // fn.apply(,[]) // Math.max(3, 5, 6); var arr = [5, 10, 1, 3, 6]; // Math.max不能求数组中的最大值 // console.log(Math.max(arr)); console.log(Math.max.apply(Math, arr)); // console.log(1, 2, 3); // console.log(arr); console.log.apply(console, arr);
函数中的属性
-
arguments
伪数组,获取到的时是函数的实参
-
caller
函数的调用者,在全局范围调用的时候caller是null
-
length
函数的形参的个数
-
name
函数的名称,字符串类型
-
函数内部有个私有
高阶函数
以下两种方式成为高阶函数
-
函数作为参数
-
函数作为返回值的时候
应用
// 函数作为返回值的时候 // 写一个函数,生成1-10之间的随机整数 function getRandom() { return parseInt(Math.random() * 10) + 1; } console.log(getRandom()); //写一个函数,生成1-10之间的随机整数 // 第一次调用生成随机数,以后每次调用都返回第一次的随机值 function getRandom() { var random = parseInt(Math.random() * 10) + 1; return function () { return random; } } var fn = getRandom(); console.log(fn()); console.log(fn()); console.log(fn());
闭包
闭包是使用被作用域封闭的遍历,函数,闭包等执行的一个函数的作用域。通常我们用和其相应的函数来指代这些作用域。(可以访问独立数据的函数)
function fn() {
var n = 10;
return function () {
return n;
}
}
var f = fn();
console.log(f());
特点
延展了函数的作用域范围
闭包案例
点击li输出他对应的索引
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul>
<li>001</li>
<li>002</li>
<li>003</li>
<li>004</li>
<li>005</li>
</ul>
<script>
var ul = document.getElementsByTagName('ul')[0];
for (var i = 0, len = ul.children.length; i < len; i++) {
var li = ul.children[i];
(function (i) {
li.onclick = function () {
console.log(i);
}
})(i)
}
</script>
</body>
</html>
定时器的执行过程
js开始执行的时候,会把所有的代码先放到执行栈中,当遇到setTimeout的时候,会把里面的匿名函数放到任务队列中,当执行栈中所有的代码处理完成之后才会处理任务队列中的代码。
console.log('start');
setTimeout(function () {
console.log('timeout');
}, 0);
console.log('over');
// 输出结果
// start over timeout
对象的拷贝
浅拷贝 - 单层复制
只可以复制原对象中的基本数据类型,而对象类型无法复制。
// 对象的拷贝
var obj1 = {
name : 'zs',
sex : '男',
dog : {
var name = '大黄';
}
}
var obj2 = {}
for (var key in obj1) {
obj2[key] = obj1[key];
}
// obj1内容中的基本类型修改不会影响obj2,但是obj1的dog对象修改会影响obj2
console.log(obj2);
深拷贝 - 多层复制
// 对象的拷贝
var obj1 = {
name : 'zs',
sex : '男',
dog : {
var name = '大黄';
}
}
var obj2 = {}
// 深拷贝(o1的成员拷贝给o2)
function deepCopy (o1, 02) {
for (var key in o1) {
// 获取key属性对应的值
var item = o1[key];
if (item instanceof Object) {
deepCopy(item, o2[key]);
} else if () {
deepCopy(item, o2[key]);
} else {
o2[key] = o1[key];
}
}
}
console.log(obj2);
遍历DOM树
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>遍历DOM树</title>
</head>
<body>
<h1>遍历 DOM 树</h1>
<p style="color: green;">Tip: 可以在遍历的回调函数中任意定制需求</p>
<div>
<ul id="list">
<li>123</li>
<li>456</li>
<li>789</li>
</ul>
<div>
<div>
<span>haha</span>
</div>
</div>
</div>
<div id="demo_node">
<ul>
<li>123</li>
</ul>
<p>hello</p>
<h2>world</h2>
<div>
<p>dsa</p>
<h3>
<span>dsads</span>
</h3>
</div>
</div>
<script>
// 遍历指定元素下所有的子元素
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);
}
}
var ul = document.getElementById('list');
loadTree(ul, function (element) {
element.onclick = function () {
console.log(this.innerText);
}
});
</script>
</body>
</html>
正则表达式
元字符
元字符 | 说明 |
---|---|
\d | 匹配数字 |
\D | 匹配任意非数字的字符 |
\w | 匹配字母或数字或下划线 |
\W | 匹配任意不是字母,数字,下划线 |
\s | 匹配任意的空白符 |
\S | 匹配任意不是空白符的字符 |
. | 匹配除换行符以外的任意单个字符 |
^ | 表示匹配行首的文本(以谁开始) |
$ | 表示匹配行尾的文本(以谁结束) |
限定符
限定符 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
##其他
[] 字符串用中括号括起来,表示匹配其中的任一字符,相当于或的意思
[^] 匹配除中括号以内的内容
\ 转义符
| 或者,选择两者中的一个。注意|将左右两边分为两部分,而不管左右两边有多长多乱
() 从两个直接量中选择一个,分组
eg:gr(a|e)y匹配gray和grey
[\u4e00-\u9fa5] 匹配汉字
声明方式
-
var regularExpression = new RegExp(模式pattern, flag);
flag
- i 忽略大小写
- g 全局匹配
-
var regularExpression = /^\d{5,12}$/;
案例
验证手机号:
^\d{11}$
验证邮编:
^\d{6}$
验证日期 2012-5-01
^\d{4}-\d{1,2}-\d{1,2}$
验证邮箱 xxx@itcast.cn:
^\w+@\w+\.\w+$
验证IP地址 192.168.1.10
^\d{1,3}\(.\d{1,3}){3}$
参数
标志 | 说明 |
---|---|
i | 忽略大小写 |
g | 全局匹配 |
gi | 全局匹配+忽略大小写 |
分割
// 1. 提取日期中的年部分 2015-5-10
// var dateStr = '2015-1-5';
// console.log(dateStr.split('-'));
// var dateStr = '2015/1-5';
// console.log(dateStr.split(/[/-]/));
// 2. 提取邮件中的每一部分
var str = 'xxxx@itcast.com';
console.log(str.split(/[@\.]/)); // 以@或者.进行分割
替换
// 1. 提取工资
var str = "张三:1000,李四:5000,王五:8000。";
var array = str.match(/\d+/g);
console.log(array);
// 2. 提取email地址
var str = "123123@xx.com,fangfang@valuedopinions.cn 286669312@qq.com 2、emailenglish@emailenglish.englishtown.com 286669312@qq.com...";
var array = str.match(/\w+@\w+\.\w+(\.\w+)?/g);
console.log(array);
// 3. 分组提取
// 3. 提取日期中的年部分 2015-5-10
var dateStr = '2016-1-5';
// 正则表达式中的()作为分组来使用,获取分组匹配到的结果用Regex.$1 $2 $3....来获取
var reg = /(\d{4})-\d{1,2}-\d{1,2}/;
if (reg.test(dateStr)) {
console.log(RegExp.$1);
}
// 4. 提取邮件中的每一部分
var reg = /(\w+)@(\w+)\.(\w+)(\.\w+)?/;
var str = "123123@xx.com";
if (reg.test(str)) {
console.log(RegExp.$1);
console.log(RegExp.$2);
console.log(RegExp.$3);
}
替换
// 1. 替换所有空白
var str = " 123AD asadf asadfasf adf ";
str = str.replace(/\s/g,"xx");
console.log(str);
// 2. 替换所有,|,
var str = "abc,efg,123,abc,123,a";
str = str.replace(/,|,/g, ".");
console.log(str);