此笔记是我在拉勾学习课程过程中的总结,文字原创,笔记里很多技巧和知识是老师总结的,如果有侵权,请联系本人!
从面向过程到面向对象的确是一个挺难的转变,但是面向对象的好处的确显而易见,更紧凑的结构,更简洁的代码,封装性也更好,不用操心对象的实现过程,传参数,得结果,调方法,都比较方便。
一、面向对象
1.什么是对象
我们可以从两次层次来理解:
(1) 对象是单个事物的抽象。
(2) 对象是一个容器,封装了属性(property)和方法(method)。
属性:对象的状态
方法:对象的行为
区分对象和类:对象是单个事物的抽象,而不是一类事物的抽象
2.面向对象编程
它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。
对比面向过程和面向对象:
• 面向过程就是亲力亲为,事无巨细,面面俱到,步步紧跟
• 面向对象就是找一个对象,指挥得结果
• 面向对象将执行者转变成指挥者
• 面向对象不是面向过程的替代,而是面向过程的封装
3.面向对象特性
封装、继承、多态(抽象)
4.创建对象的几种方式
• new Object() 构造函数
• 对象字面量 {}
• 工厂函数
• 自定义构造函数
构造函数和实例对象的关系:
构造函数是根据具体的事物抽象出来的抽象模板
每个实例对象含有一个constructor属性,并指向构造函数
但是要用 instanceof 判断一个对象的具体对象类型。
// 通过构造函数生成的对象都有一个constructor属性 ,构造器,构造函数
// 每个对象的constructor 属性值就是生成的这对象的构造函数
// 可以通过它找到 对象的出处,是原生的还是自定义的
// console.log(arr.constructor);
// console.log(person1.constructor);
// 判断一个对象的具体对象类型,需要使用instanceof 进行判断,constructor方法不严谨
console.log(person1 instanceof Person);
console.log(person1 instanceof Array);
5.实例成员和静态成员(只有构造函数生成对象的时候区分)
实例成员:在构造函数内部添加给this的成员,创建实例对象后必须有对象调用。
静态成员:添加给构造函数自身的成员,只能使用构造函数调用。
6.构造函数与原型
构造函数
- 首字母要大写
- 使用new关键字创建对象
- 浪费内存
原型特点:
• 任何函数都具有一个 prototype 属性,因为有了该属性,我们可以使用构造函数的prototype给原型对象添加属性和方法。
•proto和构造函数的原型对象prototype里面都有一个constructor属性-- 指向构造函数本身。
• 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针proto,所以在使用属性和方法时,实例对象可以向原型对象查找属性和方法
• 实例对象可以直接访问原型对象成员。
// 定义构造函数
function Person (name,age) {
this.name = name;
this.age = age;
}
// 获取构造函数的 prototype 属性
console.log(Person.prototype);
// prototype属性值是一个对象,通常叫做原型对象
// 对象属性内部可以添加一些属性和方法
Person.prototype.type = "human";
Person.prototype.sayHi = function () {
console.log('hello');
};
// 构造函数的 原型对象上面都有一个默认的 constructor 属性
// console.log(Person.prototype.constructor);
// 创建实例对象
var p1 = new Person('mike',18);
// 所有的对象都有一个 __proto__ 的属性,是一个指针,指向的就是生成实例对象的 构造函数的原型对象
console.log(p1.__proto__);
console.log(p1.__proto__ === Person.constructor);
// __proto__属性并不是标准属性,是浏览器自己根据语法自动生成的
// p1.__proto__.sayHi();
p1.sayHi();
// 在真正的开发过程中,不会用__proto__属性
- 我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。
- 因为每个构造函数都有一个prototype属性,并指向原型对象,同时其属性和方法被实例所拥有
- 解决内存浪费问题
7.原型链
原型链查找机制:
- 搜索首先从对象实例本身开始
- 如果在实例中找到了具有给定名字的属性,则返回该属性的值
- 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性
- 如果在原型对象中找到了这个属性,则返回该属性的值
更简单的原型语法
为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象,将 Person.prototype 重置到一个新的对象。
注意:原型对象会丢失 constructor 成员,所以需要手动将 constructor 指向正确的构造函数。
建议:
在定义构造函数时,可以根据成员的功能不同,分别进行设置:
- 私有成员(一般就是非函数成员)放到构造函数中
- 共享成员(一般就是函数)放到原型对象中
- 如果重置了 prototype 记得修正 constructor 的指向
function Person (name,age) {
this.name = name;
this.age = age;
}
// 更优化的解决方法,将所有的实例共享的属性和方法,都添加给原型对象,添加公用的属性和方法
// Person.prototype.type = "human";
// Person.prototype.sayName = function () {
// // 方法调用时,哪个对象调用,this就指向谁
// console.log(this.name);
// };
// Person.prototype.sayAge = function () {
// console.log(this.age);
// };
// 直接使用一个对象字面量 对原型对象赋值
Person.prototype = {
construtor : Person, // 需要手动 将constructor 属性指向正确的构造函数
type : 'human',
sayName : function () {
console.log(this.name);
}
};
// 生成实例对象
var person1 = new Person('bob',18);
var person2 = new Person('mike',20);
8.JS原生构造函数的原型对象
所有函数都有 prototype 属性对象。
JavaScript中的内置构造函数也有 prototype 原型对象属性:
- Object.prototype
- Function.prototype
- Array.prototype
- String.prototype
- Number.prototype
9.ES6中关于类的新特性
9.1class类(回到java那边了,哈哈哈)
9.2static 静态方法
9.3类的继承
call方法:改变this指向,调用函数
9.3.1对象拷贝:for...in
9.3.2借用构造函数继承构造函数的属性
9.3.3构造函数原型方法继承
组合继承
9.3.4ES6新增类的继承
二、函数进阶
1.函数的定义方式
函数声明的方式 命名函数
函数表达式 匿名函数
new Function 参数是字符串的格式
所有函数都是Function的实例 函数也是对象
很少会用到这种定义方式
// var f = new Function('console.log(123)');
var f = new Function('a','b','console.log(a+b)');
f(1,2);
2.函数的调用和this指向
// this的指向是要联系执行的上下文,在调用的时候,
是按照什么方式调用,指向是不一样的
- 原型对象的this ---> 指向创建出来的实例
- 绑定事件的函数 触发相应事件调用 this ---> 指向触发事件的对象
- 立即函数 自动调用 (function () {})() this ---> window
3.改变函数内部this指向的方法
Call、apply和bind
call() 方法:
fun.call(this,arg1, arg2, arg3, ...)
如果this指定了 null 或者 undefined 则内部 this 指向 window
apply() 方法:
fun.apply(this, [argsArray])
bind() 函数:
fun.bind(this,arg1, arg2, arg3, ...)
• this:当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。
·arg1, arg2, arg3这些参数可以绑定的时候传一部分,调用的时候传一部分,最终形成一个整体作为实参参与运算
function fun (a,b) {
console.log(this);//指向window
console.log(a+b);
}
// 函数内部在调用的时候,this有自己默认的指向
// console.dir(fun);
// fun(3,4);
// call方法
// 1.功能:第一个可以指定函数的 this,第二个可以执行函数并传参
// 2.参数:第一个参数传入指定让 this 指向的对象,第二个参数及以后,是函数参数的列表
// 3.返回值:就是函数自己的返回值
// 4.测试
var o = {
name : "zj"
};
// fun.call(o,1,2);//this指向对象 o
// apply方法
// 1.功能:第一个可以指定函数的 this,第二个可以执行函数并传参
// 2.参数:第一个参数传入指定让 this 指向的对象,第二个参数是函数的参数组成的数组
// 3.返回值:就是函数自己的返回值
// 4.测试
// fun.apply(o,[4,5]);
// bind方法
// 1.功能:第一个可以指定函数的 this,bind方法不能执行函数,但是可以传参数
// 2.参数:第一个参数传入指定让 this 指向的对象,第二个参数及以后,是函数参数的列表
// 3.返回值:返回一个新的指定了 this 的函数,也可以叫绑定函数
// 4.测试
var fn = fun.bind(o,2,3);
// console.log(fn);//已经绑定给 o 了
fn();//可以不传参数,直接调用
fn(6,7);//传了参数也没用
// 当然,如果原函数是4个参数,传入6,7就可以把他们当做实参参与运算
4.函数的其他成员
• arguments 实参集合
• arguments.callee 函数本身,arguments的一个属性
• fn.caller 函数的调用者,如果在全局调用,返回的调用者为 null。
• fn.length 形参的个数
• fn.name 函数的名称
// 看一下函数内部的成员
// 看一下函数内部的成员
function fn(a,b) {
// 实际应用中,会在函数内部直接使用 一个 arguments 的关键字
console.log(arguments);
// console.log(arguments.callee);
// 存储的是函数在调用时,传入的所有 实参 组成的一个类数组对象
console.log(fn.arguments);
// 函数的调用者,函数在哪个作用域调用,caller 就是谁,如果在全局调用,值就是 null
console.log(fn.caller);
// length 指的是形参的个数
console.log(fn.length);
// 函数的名字
console.log(fn.name);
}
// function test() {
// fn(1,2,3,4);
// }
// test();
// 灵活使用 arguments 类数组对象,可以记录所有的实参
// 模仿制作一个max方法
function max() {
// 判断实参中最大的数
var nowMax = arguments[0];
for (var i = 1 ; i < arguments.length;i++) {
if (arguments[i] > nowMax) {
nowMax = arguments[i];
}
}
return nowMax;
}
console.log(max(1,4,7,9));
5.高阶函数
// 高阶函数
// 1.函数作为另一个函数的参数
// 定义一个函数,吃饭的函数,吃完后,可以去做其他事,看电影,聊天,看书
function eat(fn) {
console.log("吃晚饭");
// 接下来的要做的事情是不固定的
fn();
}
eat(function() {
console.log("看电影");
});
// 2.函数作为一个函数的返回值
// 需求:通过同一段代码实现以下效果
// 输出: 100 + m
// 输出:1000 + m
// 输入:10000 + m
function outer(n) {
function inner (m) {
console.log(m + n);
}
}
// 在外部执行 inner函数
// 100 + m
var fun = outer(100);
fun(230);
fun(50);
var fun1 = outer(1000);
fun1(55);
6.函数闭包
函数定义时天生能记住自己生成的作用域环境(变量)和函数自己,将他们形成一个密闭环境,这就是闭包。无论函数以任何方式在任何地方调用,都会回到自己定义时的密闭环境进行执行。
- 简单来讲,就时函数能使用其定义在其外的变量,延伸了变量的使用范围。
// 体会闭包
// 将一个内部函数拿到复函数外面,观察是否还能调用复函数内部的变量
function outer() {
var a = 10;
function inner() {
console.log(a);
}
// 将inner函数作为返回值
// inner();
return inner;
}
// outer外面,是不能直接访问a变量
// outer();
// console.outer(a);
// 将 outer 执行的结果,赋值给一个变量
var inn = outer();
console.log(inn);
// 在全局调用inn,按道理应该查找全局的 a 变量
inn();
// 输出的真正结果是 10, 来自于 outer 函数内部的变量
三、ES6新特性
1.let与块级作用域
2.const恒量\常量
一旦赋值不可修改,且不可先定义后期再赋值。
var,let和const的使用规律
- 不用var ,
- 主用const :后期不能被更改的数据
- 配合let:后期可更改的数据
3.数组解构与对象解构
数组解构--获取数组内部项
对象解构--获取对象内部属性
4.模板字符串与应用
// 模板字符串用 `` 表示
// const str = `this is
// a \`string`
// console.log(str);
// 支持插值表达式,在字符串中插入值、
//可以插入符合js语法的任何语句
const name = "tom"
const str = `hello,${name},${1 + 1},${Math.random()}`
console.log(str);
模板字符串标签函数
5.字符串扩展方法
- startWith()
- endsWidth()
- Includes()
6.参数默认值
7.箭头函数
- 箭头函数的this指向:
箭头函数内部不会改变this的指向,没有this机制,查找this的时候,都会到函数外部寻找this
8.对象字面量增强
9.object.assign方法
新增数据解构
10.set数据结构(不允许重复)
可用于数组去重
11.MAP数据结构
12.symbol类型
最主要的作用就是为对象添加独一无二的属性标识符
// Symbol补充
console.log(Symbol("fu") === Symbol("fu"));
// 重复使用symbol,使用全局变量
const a = Symbol.for(true)//布尔会转成字符
const b = Symbol.for("true")
console.log(a === b);//true
// 内置symbol常量
// const obj = {
// [Symbol.toStringTag]: "xojjdlag"
// }
// console.log(obj.toString());//[object xojjdlag]
const obj ={
[Symbol()]: "symbol value",
foo: "foo value"
}
console.log(Object.keys(obj));// ['foo']
for(var k in obj) {
console.log(k);//foo
}
console.log(JSON.stringify(obj));//{"foo":"foo value"}
console.log(Object.getOwnPropertySymbols(obj));//[Symbol()]
13.for of 循环
四、正则表达式
1.构造方式
1.使用一个正则表达式字面量,如下所示:
var reg = /abc/;
2.调用 RegExp 对象的构造函数,如下所示:
var re = new RegExp("abc");
第二种性能较差
2.正则方法
字符串方法
正则表达式方法;
// exec方法:查找匹配的字符串,输出到数组中,返回一个数组
// 不论有没有全局修饰符,都只会找到第一个后结束
var str = 'aaaabccccbacabc';
var reg = /abc/;
var arr = reg.exec(str);
console.log(arr);
// test方法:检测字符串中是否满足正则表达式的匹配规则,返回布尔值
var reg = /abc/;
console.log(reg.test('aaaabccccbacabc'));
特殊字符
- 特殊字符:javascript 中常用特殊字符有 ( ) [ ] { } \ ^ $ | ? * + .
- 若想匹配这类字符必须用转移符号 \ 如:(,^,\
预定义特殊字符 | 说明 | 举例 |
---|---|---|
\t | 制表符 | /\t/ |
\n | 回车符 | /\n/ |
\f | 换页符 | /\f/ |
\b | 空格 | /\b/ |
边界符 | 说明 | 举例 |
---|---|---|
^ | 开头 不能紧跟于左中括号的后面 | /^hello/.test('hello javascript') => true |
$ | 结尾 | /javascript$/.test('hello javascript') => true |
预定义类
预定义类 | 举例 | 说明 |
---|---|---|
. | [^\n\r] | 除了换行和回车之外的任意字符 |
\d | [0-9] | 数字字符 |
\D | [^0-9] | 非数字字符 |
\s | [ \t\n\x0B\f\r] | 空白字符 |
\S | [^ \t\n\x0B\f\r] | 非空白字符\w [a-zA-Z_0-9] 单词字符(所有的字母/数字/下划线) |
\W | [^a-zA-Z_0-9] | 非单词字符 |
量词:出现次数
量词类 | 举例 | 说明 |
---|---|---|
{n} | 硬性量词 | 对应零次或者n次 |
{n,m} | 软性量词 | 至少出现n次但不超过m次(中间不能有空格) |
{n,} | 软性量词 | 至少出现n次(+的升级版) |
? | 软性量词 | 出现零次或一次 |
* | 软性量词 | 出现零次或多次(任意次) |
+ | 软性量词 | 出现一次或多次(至少一次) |
分组
小括号表示分组()
// 分组
var reg = /^(bye){2}$/;
console.log(reg.test("byebye"));
console.log(reg.test("bbyyee"));//false
或操作符 |
可以使用竖线(|)字符表示或者的关系。
/a|bcd/ 匹配 a 或 bcd 字符。
/(ab)+|(cd)+/ 匹配出现一次或多次的 ab 或者 cd
举例
分组反向引用
// 正则中通过分组匹配到字符串中,会被进行编号,从1开始
// 在正则内部可以通过 \1 方式,去对字符串进行反向引用
console.log(/^([a-z]{3})\1$/.test("byebye"));//true
console.log(/^([a-z]{3})\1$/.test("byebye"));//false
// 正则表达式以外通过 $1,进行字符串的引用
// var str = "123*456".replace(/^(\d{3})\*(\d{3})$/,"$2*$1");
// console.log(str);
// 第二个参数可以是一个函数
var str = "123*456".replace(/^(\d{3})\*(\d{3})$/,function(match,$1,$2){
return $2 * 2 + "/" + $1 * 3;
});
中文字符
匹配中文:
[\u4e00-\u9fa5]