赞
踩
ES6之前 → 通过 构造函数 + 原型
实现面向对象编程
__proto__
原型指向构造函数的原型对象语法糖
。语法糖就是一种便捷写法,简单理解,有两种方法可以实现同样的功能,但是一种写法更加清晰、方便,那么这个方法就是语法糖。
class name{
// class body
}
// 创建实例 类必须使用new 实例化对象
var xx = new name();
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
say(){
console.log(this.name + 'Hello!');
}
}
// 创建实例
var ldh = new Person('刘德华',18);
console.log(ldh.name) // 刘德华
子类可以继承父类的一些属性和方法。
class Father{ // 父类
}
class Son extends Father{ // 子类继承父类
}
实例:
class Father{
constructor(surname){
this.name = surname;
}
say(){
console.log('你的姓是' + this.name);
}
}
class Son extends Father{ // 子类继承了父类
}
var ss = new Son('廖')
ss.say(); // 你的姓是廖
调用父类的构造函数,也可以调用父类的普通函数。
class Father{ constructor(surname){ this.surname = surname; } saySurname(){ console.log('我的姓是' + this.surname); } } class Son extends Father{ constructor(surname,firstname){ // 子承父类 super(surname); // 调用父类constructor(surname) this.firstname = firstname; // 定义子类独有属性 } sayFirstname(){ console.log('我的名字是:' + this.firstname); } } var ss = new Son('吴','彦祖') ss.saySurname(); ss.sayFirstname();
调用父类的普通函数
class Father{
say(){
return '我是父类 -->'
}
}
class Son extends Father{
say(){
// super.say() super 调用父类方法
return super.say() + '的子类';
}
}
var dome = new Son();
console.log(dome.say()); // 我是父类!的子类
super 必须在子类this之前调用。
子类在构造函数中使用super, 必须放到 this 前面 (必须先调用父类的构造方法,在使用子类构造方法)
constructor
里面的 this 指向实例对象,方法里的 this
指向这个方法的调用者。用法 | 说明 |
---|---|
insertAdjacentHTML(‘追加的位置’,‘追加的字符’) | 用字符串创建的元素追加到父元素里面 |
beforeend | 插入元素内部的最后一个子节点之后 |
ondblclick | 双击事件 |
input.select() | 文本框内容处于选中状态 |
双击禁止选中文字:
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
insertAdjacentHTML(position,text);
// 创建li元素和section元素
var li = '<li class="liactive"><span>测试1</span><span class="iconfont icon-guanbi"></span></li>';
// 把这两个元素追加到对应的父元素里面 beforeend(父元素内容的后面)
that.ul.insertAdjacentHTML('beforeend', li)
let 声明的变量时只在所处于的块级有效
if (true) {
let b = 20;
console.log(b)
if (true) {
let c = 30;
}
console.log(c); // c is not defined
}
console.log(b) // b is not defined
不存在变量提升
console.log(a); // a is not defined
let a = 20;
存在暂时性死区问题
var num = 10
if (true) {
console.log(num); // Cannot access 'num' before initialization
let num = 20;
}
防止循环变量变成全局变量
```js
for (let i = 0; i < 5; i++) {
console.log(i);
}
// 依次输出 0 1 2 3 4 5
```
作用:声明常量,常量就是值(内存地址) 不能变化的量
if (true) { const a = 10; if (true) { const a = 20; console.log(a); // 20 } console.log(a); // 10 } console.log(a); // a is not defined ``` 2. 声明常量时必须赋值 ```js const PI; // Missing initializer in const declaration
const = PI;
PI = 100; // assignment to constant variable.
const ary = [100, 200];
ary[0] = 'a';
ary[1] = 'b';
console.log(ary); // ['a', 'b'];
ary = ['a','b']; // assignment to constant variable.
ES6 中允许从数值中提取值,按照对应位置,对变量赋值。对象也可以实现解构。
// 数组解构允许我们按照一一对应的关系从数组中提取值 然后将值赋值给变量
let ary = [1,2,3];
let [a, b, c, d, e] = ary;
console.log(a,b,c) // 1 2 3
console.log(e) // undefined
// 如果解构不成功,变量的值为undefined
let [foo] = [];
let [bar, foo] = [1];
let person = { name: 'zhangsan', age: 20 };
// 从 person对象中解构(匹配) name 和 age 属性
let { name, age } = person;
console.log(name); // 'zhangsan'
console.log(age); // 20
let {name: myName, age: myAge} = person; // myName myAge 属性别名
console.log(myName); // 'zhangsan'
console.log(myAge); // 20
写法:
() => {}
const fn = () => {}
function sum(num1, num2) {
return num1 + num2;
}
const sum = (num1, num2) => num1 + num2;
function fn(v){
retuen v;
}
const fn = v => v;
const obj = { name: '张三'}
function fn () {
console.log(this); // obj
return () => {
console.log(this) // obj
}
}
const resFn = fn.call(obj);
resFn();
箭头函数面试题:
var age = 100;
var obj = {
age: 20,
say: () => {
alert(this.age) // 100
}
}
obj.say();
在最后的形参名前加
...
function sum (first, ...args) {
console.log(first); // 10
console.log(args); // [20, 30]
}
sun(10, 20, 30);
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // wangwu
console.log(s2); // ['zhangsan', 'lisi']
let aty = [1, 2, 3];
// ...ary // 1, 2, 3
console.log(...ary); // 1 2 3
console.log(1, 2, 3); // 1 2 3
//方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二
ary1.push(...ary2);
let oDivs = document.getElemenetsByTagName('div'); // HTMLCollection(10)
oDivs = [...oDivs]; // Array(7)
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
let arrayLike = {
"0": 1,
"1": 2,
"length": 2
}
let newAry = Array.from(aryLike, item => item * 2)
返回第一个符合条件元素,如果没有找到返回undefined
let ary = [{
id: 1,
name: '张三'
},{
id: 2,
name: '李四'
}];
let target = ary.find((item, index) => item.id == 2);
返回第一个符合条件元素索引位置,如果没有找到返回undefined
let ary =[1, 5, 10, 15];
let index = ary.findIndex((value,index) => value > 9);
console.log(index); // 2
查找某个数组是否包含给定的值,返回布尔值。
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
let ary = ["a", "b", "c"];
console.log(ary.includes('a'))
ES6 新增的创建字符串的方式,使用反引号定义。
let name = '张三';
let sayHello = `hello,my name is ${name}`; // hello,my name is zhangsan
let resilt = {
name: 'zhangsan',
age: 20,
sex:'男'
}
let html = ` <div>
<span>${result.name}</span>
<span>${result.name}</span>
<span>${result.name}</span>
</div> `;
colst aryHello = function () {
return '你好 世界!';
};
let greet = `${sayHello()} 哈哈哈哈`;
console.log(greet); // 你好 世界!
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
// 创建一个set() 数据结构
const s = new Set;
const set = new Set([1, 2 ,3 ,4 , 5]);
方法 | 说明 |
---|---|
add(value) | 添加某个值,返回Set结构本身 |
delete(value) | 表示删除是否成功, 返回布尔值 |
has(value) | 表示该值是否为Set的成员, 返回布尔值 |
clear() | 清除所有成员 |
示例:
const s = new Set();
s.add(1).add(2).add(3); // 向 set 结构中添加值
s.delete(2) // 删除 set 结构中的2值
s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值
s.clear() // 清除 set 结构中的所有值
// 遍历set数据结构 从中取值
const s = new Set(['a', 'b', 'c']);
s.forEach(value => {
console.log(value)
})
正则表通常被用来
提取和替换
那些符合某个模式(规则)的文本。列如验证表单、过滤敏感词、从字符串中获取我们想要的特定部分。
// 1. 利用 RegExp对象来创建 正则表达式
var regexp = new RegExp(/123/);
console.log(regexp);
// 2. 利用字面量创建 正则表达式
var rg = /123/;
正则对象方法,用于检测字符串是否符合该规则,返回 true 或 false
var rg = /123/;
console.log(rg.test(123));
边界符 | 说明 |
---|---|
^ | 表示匹配行首的文本(以谁开始) |
$ | 表示匹配行尾的文本(以谁结束) |
[]
内。var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
console.log(rg.test('andy'));
// 2. 字符组合 匹配26个英文字母和数字 1-9
/^[a-z0-9]$/.test('a') // true
// 3. [^] 方括号内部 取反符^
/^[^abc]$/.test('a') // false
量词符用来设定某个模式出现的次数。
量词 | 说明 |
---|---|
* | 匹配零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
括号 | 说明 |
---|---|
{} 量词符 | 括号内表示重复次数 |
[] 字符集合 | 匹配方括号中的任意字符 |
() 分组 | 表示优先级 |
预定义类指的是某些常见模式的简写方式
预定义 | 说明 | 等价于 |
---|---|---|
\d | 匹配0-9之间的任一数字 | [0-9] |
\D | 匹配所有0-9以外的字符 | [ ^0-9] |
\w | 匹配任意的字母、数字和下划线 | [A-Za-z0-9_] |
\W | 除所有字母、数字和下划线以外的字符 | [ ^A-Za-z0-9_] |
\s | 匹配空格 (包括换行符、制表符、空格符等) | [\t\r\n\v\f] |
\S | 匹配非空格的字符 | [ ^\t\r\n\v\f] |
匹配模式 | 说明 |
---|---|
g | 全局匹配 (默认只匹配一个) |
i | 忽略大小写 |
gi | 全局匹配+忽略大小写 |
使用replace() 方法替换,返回值是替换完毕的新字符串。
stringObject.replace(regexp/substr,replacement)
<textarea name="" id="message"></textarea> <button>提交</button>
<div></div>
// 替换 replace
var text = document.querySelector('textarea');
var btn = document.querySelector('button');
var div = document.querySelector('div');
btn.onclick = function() {
div.innerHTML = text.value.replace(/激情|gay/g, '**');
}
在 ES6之前 ,对象不是基于类创建的,而是用构建函数的特殊函数来定义对象和它们的特征。
构造函数
是一种特殊的函数,使用 new 来初始化对象,把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
构造函数存在浪费内存问题
解决办法:我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
构造函数通过原型分配的函数是所有对象所共享的。
原型的作用就是 共享方法。
对象都会有一个属性
__ proto __
指向构造函数的prototype
原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __ proto __ 原型的存在。
__proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
实例:
function Star(uname, age) { this.uname = uname; this.age = age; } // 我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上 Star.prototype.sing = function() { console.log('我会唱歌'); } var ldh = new Star('薛之谦', 18); var zxy = new Star('灵境', 19); console.log(ldh.sing === zxy.sing); // true ldh.sing(); zxy.sing(); // 对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype console.log(ldh); console.log(ldh.__proto__ === Star.prototype); // true
__ proto __ 对象原型和原型对象 prototype 是等价的
方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的 sing
如果么有sing 这个方法,因为有__proto__
的存在,就去构造函数原型对象 prototype 身上去查找sing这个方法。
对象都会有一个属性 __ proto __ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __ proto __ 原型的存在。
constructor 主要用于记录该对象引用那个构造函数
,它可以让原型对象重新指向原来的构造函数。
注意:
很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数。
function Star(uname, age) { this.uname = uname; this.age = age; } // 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数 // Star.prototype.sing = function() { // console.log('我会唱歌'); // }; // Star.prototype.movie = function() { // console.log('我会演电影'); // } Star.prototype = { // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数 constructor: Star, sing: function() { console.log('我会唱歌'); }, movie: function() { console.log('我会演电影'); } } var ldh = new Star('薛之谦', 28); var zxy = new Star('灵境', 26); console.log(Star.prototype); // Object console.log(ldh.__proto__); // Object console.log(Star.prototype.constructor); console.log(Star.prototype.constructor === ldh.__proto__.constructor); // true
构造函数、实例、原型对象之间的关系
当访问一个对象的属性时首先查找这个对象自身有没有该属性。
如果没有就找原型(__proto__指向的 prototype
)。
如果还没有就查找原型对象的原型(Object的原型对象)。
一直找到 Object 为止(null)。
原型对象里面方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象.
通过原型对象,对原来的内置对象进行扩展自定义的方法,比如给对象增加自定义求偶数和的功能。
Array.prototype.sum = function(){
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
var arr = [1, 2, 3];
console.log(arr.sum());
使用对象的形式创建方法会被覆盖
(如: Array.prototype = {} ),所以数组和字符串内置对象只能是 Array.prototype.xxx = function(){} 的方式。
call (设置调用时指向的对象, 传递其他参数…)
function fn(x, y) {
console.log('我想喝手磨咖啡');
console.log(this);
console.log(x + y);
}
var o = {
name: 'andy'
};
// fn.call(); 1. 使用call() 可以调用函数
// 2. 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 1, 2);
ES6之前并没有给我们提供
extends
继承。我们可以通过构造函数+原型对象
模拟实现继承
,被称为组合继承。
// 1. 父构造函数 function Father(uname, age) { // this 指向父构造函数的对象实例 this.uname = uname; this.age = age; } // 定义共享方法 Father.prototype.money = function() { console.log(100000); }; // 2 .子构造函数 function Son(uname, age, score) { // 借用父构造函数继承属性 使用call()设置调用时this为 子构造函数的this Father.call(this, uname, age); // this 指向子构造函数的对象实例 this.score = score; } // Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化 Son.prototype = new Father(); // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数 Son.prototype.constructor = Son; // 这个是子构造函数专门的方法 Son.prototype.exam = function() { console.log('孩子要考试'); } var son = new Son('张全蛋', 28, 100); console.log(son); console.log(Father.prototype); console.log(Son.prototype.constructor);
- 迭代(遍历)方法:
forEach()、map()、filter()、 some()、every()
不打算改变数据的时候
(打印或存入数据库)array.forEach(function(currentValue, index, arr))
currentValue : 每一个数组元素
index :数组当前项的索引
arr : 数组对象本身
let arr = [1, 2, 3, 4, 5];
// 使用map将每一个元素乘以它们自身,然后筛选出那些大于10的元素,最终结果赋值给arr2。
let arr2 = arr.map(value => value * value).filter(value => value > 10);
// arr2 = [16, 25]
筛选时直接返回一个新数组。
array.filter(function(currentValue, index, arr))
实例:
var arr = [12,66,88,99,56,4,3]
var newarr = arr.filter(function(value,index){
// 筛选数组中大于20的值
return value > = 20;
//return value % 2 === 0; // 返回偶数
});
console.log(newarr);
返回布尔值, 找到第一个满足条件的元素则终止循环,不再继续查找。
var arr1 = ['red', 'pink', 'blue'];
var flag1 = arr1.some(function(value) {
return value == 'pink';
});
console.log(flag1); // true
filter返回一个新数组,含多个元素,find返回第一个符合条件元素。
indexof() 方法可返回某个指定字符串值在字符串中首次出现的位置。
var str = ' an dy ';
var str1 = str.trim();
获取对象自身所有的属性
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
};
// 获取对象自身所有的属性
var arr = Object.keys(obj);
// 使用 Object.keys() 获取所有属性后遍历对象
arr.forEach(function(value) {
console.log(value);
})
定义新属性或修改原有的属性
Object.defineproperty(obj, prop, descriptor)
第三个参数 descriptor 说明:
var obj = { id: 1, pname: '小米', price: 1999 }; Object.defineProperty(obj, 'address', { value: '小米科技有限公司', // 如果值为false 不允许修改这个属性值 默认值也是false writable: true, // enumerable 如果值为false 则不允许遍历, 默认的值是 false enumerable: true, // configurable 如果为false 则不允许删除这个属性 默认为false configurable: true }); console.log(obj.address);
function fn() {
console.log('Hello !');
}
// fn(); fn.call()
var o = {
sayHi: function() {
console.log('人生的巅峰');
}
}
o.sayHi();
function Star() {
// do somesing...
};
new Star();
btn.onclick = function() {}; // 点击了按钮就可以调用这个函数
setInterval(function() {}, 1000); // 这个函数是定时器自动1秒钟调用一次
// 立即执行函数是自调用
(function() {
console.log('hello 2021');
})();
调用方式的不同决定了this 的指向不同。
调用方式 | this指向 |
---|---|
普通函数调用 | window |
构造函数调用 | (创建的) 实例对象 原型对象里面的方法指向实例对象 |
对象方法调用 | 改方法所属对象 |
事件绑定方法 | 绑定事件的那个对象 |
定时器函数 | window |
立即执行函数 | window |
详解:
<button>点击</button> <script> // 函数的不同调用方式决定了this 的指向不同 // 1. 普通函数 this 指向window function fn() { console.log('普通函数的this' + this); } window.fn(); // 2. 对象的方法 this指向的是对象 o var o = { sayHi: function() { console.log('对象方法的this:' + this); } } o.sayHi(); // 3. 构造函数 this 指向 ldh 这个实例对象 原型对象里面的this 指向的也是 ldh这个实例对象 function Star() {}; Star.prototype.sing = function() { } var ldh = new Star(); // 4. 绑定事件函数 this 指向的是函数的调用者 btn这个按钮对象 var btn = document.querySelector('button'); btn.onclick = function() { console.log('绑定时间函数的this:' + this); }; // 5. 定时器函数 this 指向的也是window window.setTimeout(function() { console.log('定时器的this:' + this); }, 1000); // 6. 立即执行函数 this还是指向window (function() { console.log('立即执行函数的this' + this); })(); </script>
使用 bind()、call()、apply() 三种方法可以优雅的解决函数内部的 this 指向问题。
var o = {
name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a + b);
};
// 1. call() 调用函数同时改变this指向
fn.call(o, 1, 2);
// apply() 调用函数同时改变this指向,参数必须是数组(伪数组)
fn.apply(o, ['pink']);
示例: 我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮:
<button>点击</button>
<button>点击</button>
<button>点击</button>
var btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
this.disabled = true;
setTimeout(function() {
this.disabled = false;
}.bind(this), 2000);
}
}
总结:
相同点:
不同点:
应用场景:
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
function fn(a, b, callback) {
console.log(a + b);
callback && callback();
}
fn(7, 10, function() {
console.log('我是最后调用的');
});
函数也是一种数据类型,同样可以作为参数,传递给另一个参数使用,最典型的就是作为回调函数
。
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
退出条件 return
实例: 利用递归函数求1~n的阶乘
// 利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n function fn(n) { if (n == 1) { return 1; } return n * fn(n - 1); } console.log(fn(3)); console.log(fn(4)); // 详细思路 假如用户输入的是 3 //return 3 * fn(2) //return 3 * (2 * fn(1)) //return 3 * (2 * 1) //return 3 * (2) //return 6
实例2: 利用递归函数求斐波那契数列 (兔子序列)
// 1、1、2、3、5、8、13、21...
// 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.log(fb(3));
console.log(fb(6));
分为全局变量局部变量
一个作用域可以访问另外一个函数内部的局部变量。
延伸了变量的作用范围
我们函数外面的作用域可以访问函数内部的局部变量。
function fn1(){ // fn1 就是闭包函数
var num = 10;
function fn2(){
console.log(num); // 10
}
fn2()
}
思考题1:
var name = "The Window";
var obj1 = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
console.log(obj1.getNameFunc()()) // The Window
思考题2 :
var name = "The Window";
var obj2 = {
name: "My Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name;
};
}
};
console.log(obj2.getNameFunc()()) // My Object
Object.assign(target, …sources)
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
}
};
var o = {};
Object.assign(o, obj);
<script>
"use strict";
console.log("这是严格模式。");
</script>
有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在立即执行的匿名函数之中,这样独立创建一个作用域而不影响其他 script脚本文件。
(function(){
"use strict";
var num = 10;
function fn(){}
})();
为函数开启严格模式
// 此时只是给fn函数开启严格模式
function fn() {
'use strict';
// 下面的代码按照严格模式执行
}
变量常规:
严格模式下 this 指向问题:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。