前端点滴(JS进阶)(四)----倾尽所有
杂项
1. arguments对象
arguments对象,存在于函数的内部,它能够表达函数的实际参数(实参),除此以外,arguments对象还有一个属性callee,它表示函数的名字,arguments的length属性表示实参的个数。
- 查询形参个数:arguments.callee.length
- 查询实参个数:arguments.length
小例子:实现实参的和
2. this 指向问题
this永远指向一个对象。
this指向的对象是谁,关键要看this运行的环境。也就是this所在的函数被赋值给哪个对象了,那么this就表示这个对象。
this 的指向
(1)全局环境下 this 表示window对象
(2)事件处理函数中的 this 指向
(3)面向对象中的 this 指向
区分this表示哪个对象,关键看调用函数的时候,函数是由谁来调用的。由o1调用,this就表示o1,由o2调用,this表示o2 。
(4)定时器中的 this 指向
修改 this 指向
(1)apply 函数
语法: 函数.apply(需要指向的对象,[参数1, 参数2…]);
说明:
- 参数一与参数二必须以数组类型输入。
- 调用后会马上执行函数
实例:
/* apply 改变this 指向 */
var obj1 = {
name:'chen'
};
var obj2 = {
name:'yaodao',
say:function(age,height){
console.log('名字为'+this.name+'年龄'+age+'身高'+height+'cm');
}
}
obj2.say.apply(obj1,[20,167] ) //=> "名字为chen年龄20身高167cm"
(2)call 函数
语法: 函数.call(需要指向的对象,参数1, 参数2…);
说明:
- 调用后会马上执行函数
实例:
/* call 改变this 指向 */
var obj1 = {
name:'chen'
};
var obj2 = {
name:'yaodao',
say:function(age,height){
console.log('名字为'+this.name+'年龄'+age+'身高'+height+'cm');
}
}
obj2.say.call(obj1,20,167) //=> "名字为chen年龄20身高167cm"
(3)bind 函数
语法: 函数.bind(需要指向的对象,参数1, 参数2…);
说明:
- 调用后不会马上执行函数,还需调用。
实例:
/* bind 改变this 指向 */
var obj1 = {
name:'chen'
};
var obj2 = {
name:'yaodao',
say:function(age,height){
console.log('名字为'+this.name+'年龄'+age+'身高'+height+'cm');
}
}
obj2.say.bind(obj1,20,167)(); // 再调用一次。
/* 写法二 */
obj2.say.bind(obj1)(20,167); //=> "名字为chen年龄20身高167cm"
//实际上obj2.say.bind(obj1)返回改变了this指向的say方法
小实例:寻找数组的最大值并返回数组
注意: 不会改变原始数组。
var arr = [1,3,5,7,2,46,7,3,73,24];
console.log(Math.max.apply(null,arr))
JS面向对象编程特性
面向对象编程就是基于对象的编程。面向对象编程简称OOP(Object-Oritened Programming)为软件开发人员敞开了一扇大门,它使得代码的编写更加简洁、高效、可读性和维护性增强。它实现了软件工程的三大目标:(代码)重用性、(功能)扩展性和(操作)灵活性,它的实现是依赖于面向对象的三大特性:封装、继承、多态。在实际开发中 使用面向对象编程 可以实现系统化、模块化和结构化的设计 它是每位软件开发员不可或缺的一项技能。
1. 封装
封装(概念) 封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。封装是面向对象的特征之一,是对象和类概念的主要特性。 简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
(1)es5 封装
function Person(name,age,sex){
if (!(this instanceof Person)) {
return new Person(name, age, sex);
}
this.name = name;
this.age = age;
this.sex = sex||'female';
this.walk = function() {
if (this.age <= 2) {
return console.log('我不会走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我会走路了');
}
return console.log('可以跑了');
}
this.study = function (skill) {
console.log('学习' + skill);
}
this.introduce = function () {
console.log(`我是${this.name},我是一个${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}岁了。`);
}
}
// 调用方式
// new 关键字创建实例
var p = new Person('小明', 10, 'male');
// 直接调用创建
var p1 = Person('小红', 9, 'female');
p.walk(); //可以跑了
p1.study('游泳'); // 学习游泳
p.introduce(); // 我是小明,我是一个男孩,今年 10 岁了。
p1.introduce(); // 我是小红,我是一个女孩,今年 9 岁了。
使用原型链方式
function Person(name,age,sex){
if (!(this instanceof Person)) {
return new Person(name, age, sex);
}
this.name = name;
this.age = age;
this.sex = sex||'female';
Person.prototype.walk = function() {
if (this.age <= 2) {
return console.log('我不会走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我会走路了');
}
return console.log('可以跑了');
}
Person.prototype.study = function (skill) {
console.log('学习' + skill);
}
Person.prototype.introduce = function () {
console.log(`我是${this.name},我是一个${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}岁了。`);
}
}
// 调用方式
// new 关键字创建实例
var p = new Person('小明', 10, 'male');
// 直接调用创建
var p1 = Person('小红', 9, 'female');
p.walk(); //可以跑了
p1.study('游泳'); // 学习游泳
p.introduce(); // 我是小明,我是一个男孩,今年 10 岁了。
p1.introduce(); // 我是小红,我是一个女孩,今年 9 岁了。
(2)es6 封装
特点:必须使用 new 创建实例化对象。
class Person{
/* 定义构造器 */
constructor(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex||'female'
}
/* 原型 */
walk() {
if (this.age <= 2) {
return console.log('我不会走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我会走路了');
}
return console.log('可以跑了');
}
study(skill) {
console.log('学习' + skill);
}
introduce() {
console.log(`我是${this.name},我是一个${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}岁了。`);
}
}
// 调用方式
// new 关键字创建实例
var p = new Person('小明', 10, 'male');
// 直接调用创建
// var p1 = Person('小红', 9, 'female'); // "TypeError: Class constructor Person cannot be invoked without 'new'
// 必须使用new创建实例化对象!!!!!!!!!
var p1 = new Person('小红', 9, 'female');
p.walk(); //可以跑了
p1.study('游泳'); // 学习游泳
p.introduce(); // 我是小明,我是一个男孩,今年 10 岁了。
p1.introduce(); // 我是小红,我是一个女孩,今年 9 岁了。
2. 继承
继承(概念) 继承是指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”( Inheritance )和“组合”( Composition )来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
(1)es5 继承
call,apply,bind(要注意) 方法改变 this 指向实现继承
function Person(name, sex, age) {
if (!(this instanceof Person)) {
return new Person(name, sex, age);
}
this.name = name;
this.sex = sex || 'female';
this.age = age || 0;
this.walk = function() {
if (this.age <= 2) {
return console.log('我不会走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我会走路了');
}
return console.log('可以跑了');
}
this.study = function (skill) {
console.log('学习' + skill);
}
this.introduce = function () {
console.log(`我是${this.name},我是一个${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}岁了。`);
}
}
function Boy(name, age) {
var obj = Person.call(this, name, 'male', age);
// var obj = Person.apply(this,[name, 'male', age])
// var obj = Person.bind(this,name, 'male', age)
obj.doHouseWork = function() {
console.log('我在做家务');
}
return obj
}
let boy = Boy('小米', 16); //let boy = Boy('小米', 16)();
boy.introduce(boy); // 我是小米,我是一个男孩,今年 16 岁了。
boy.doHouseWork();// 我在做家务
Object.creat() 实现继承
function create(obj) {
return Object.create(obj);
}
var person = {
name: 'Tom',
age: 20,
sex: 'male',
walk: function() {
if (this.age <= 2) {
return console.log('我不会走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我会走路了');
}
return console.log('走路');
},
study: function(skill) {
console.log('学习' + skill);
},
introduce: function() {
console.log(`我是${this.name},我是一个${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}岁了。`);
}
};
var boy = create(person);
boy.age = 15,
boy.name = '晓东';
boy.sex = 'male';
boy.doHouseWork = function() {
console.log('我在做家务');
}
boy.introduce(); // 我是晓东,我是一个男孩,今年 15 岁了
boy.doHouseWork();// 我在做家务
原型链实现继承
function Person(name, sex, age) {
if (!(this instanceof Person)) {
return new Person(name, sex, age);
}
this.name = name;
this.sex = sex || 'female';
this.age = age || 0;
Person.prototype.walk = function() {
if (this.age <= 2) {
return console.log('我不会走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我会走路了');
}
return console.log('可以跑了');
}
Person.prototype.study = function (skill) {
console.log('学习' + skill);
}
Person.prototype.introduce = function () {
console.log(`我是${this.name},我是一个${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}岁了。`);
}
}
function Boy(name, age) {
this.name = name;
this.age = age;
this.sex = 'male';
Boy.prototype.doHouseWork = function() {
console.log('我在做家务');
}
}
Boy.prototype = new Person(); //继承
Boy.prototype.constructor = Boy;
var boy = new Boy('汤姆', 12);
boy.introduce(); // 我是汤姆,我是一个男孩,今年 12 岁了。
boy.doHouseWork();// 我在做家务
console.log(boy instanceof Boy);// true
深浅拷贝实现继承
用法请看深浅拷贝实例。
(2)es6 继承
extends super关键字 ——(继承、调用)关键字
class Person {
/* 构造器 */
constructor(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
/* 原型 */
walk() {
if (this.age <= 2) {
return console.log('我不会走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我会走路了');
}
return console.log('走路');
}
study(skill) {
console.log('学习' + skill);
}
introduce() {
console.log(`我是${this.name},我是一个${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}岁了。`);
}
}
class Boy extends Person { // extends关键字继承
/* 构造器 */
constructor(name, age) {
/* super语法可以调用父对象上的函数 */
super(name, 'male', age);
}
doHouseWork() {
console.log('我在做家务');
}
}
var boy = new Boy('汤姆', 14);
boy.introduce(); // 我是汤姆,我是一个男孩,今年 12 岁了。
boy.doHouseWork();// 我在做家务
console.log(boy instanceof Boy);// true
参考文章:https://www.cnblogs.com/qiqingfu/p/10238398.html
3. 多态
多态 多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
参考文章:https://segmentfault.com/a/1190000017452120
深浅拷贝
1. 为什么使用深浅拷贝?
因为基本类型是不可变的,任何方法都无法改变一个基本类型的值,也不可以给基本类型添加属性或者方法。但是可以为引用类型添加属性和方法,也可以删除其属性和方法。
基本类型和引用类型在内存中的存储方式也大不相同,基本类型保存在栈内存中,而引用类型保存在堆内存中。为什么要分两种保存方式呢? 因为保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,但是我们可以把它的地址写在栈内存中以供我们访问。
因为这种保存方式的存在,所以我们在操作变量的时候,如果是基本数据类型,则按值访问,操作的就是变量保存的值;如果是引用类型的值,我们只是通过保存在变量中的引用类型的地址类操作实际对象。从而也引出了所谓的深浅拷贝问题。
/* 例子 */
let a = 10;
let obj = {
a:15
};
let a2 = a;
let obj2 = obj;
/* 对a2,obj2进行修改 */
a2 = 20;
obj2.a = 30;
console.log(a); //=> 10
console.log(obj.a); //=> 30 发现被修改了,影响了引用类型的原始值
内存分布:
2. 浅拷贝
浅拷贝只是复制基本类型的数据或者指向某个对象的指针,而不是复制对象本身,源对象和目标对象共享同一块内存;若对目标对象进行修改,存在源对象被篡改的可能。
浅拷贝的实现:
/* 定义一个源对象 */
let sourceObj = {
age:20,
name:'yaodao',
boolean:true,
null:null,
undefined:undefined,
threeD:[95,96,97],
say:function(language){
console.log(language)
},
otherObj:{name:"chen",age:15}
}
/* 浅拷贝 */
function copy(sourceObj){
let targetObj = Array.isArray(sourceObj) ? [] : {};
let clone;
for (var key in sourceObj) {
/* 交付地址 */
clone = sourceObj[key];
targetObj[key] = clone;
}
return targetObj;
}
let copyObj = copy(sourceObj);
/* 修改对象的参数 */
copyObj.age = 25;
copyObj.name= 'chen';
copyObj.boolean = false;
copyObj.threeD[0] = 100;
copyObj.otherObj.name= 'chen';
copyObj.otherObj.age= 20 ;
copyObj.say(Chinese) // 具有继承的效果
console.log(copyObj)
console.log(sourceObj)
输出结果:
内存中的存在形式:
3. 深拷贝
深拷贝能够实现真正意义上的对象的拷贝,实现方法就是递归调用“浅拷贝”。深拷贝会创造一个一模一样的对象,其内容地址是自助分配的,拷贝结束之后,内存中的值是完全相同的,但是内存地址是不一样的,目标对象跟源对象不共享内存,修改任何一方的值,不会对另外一方造成影响。
/* 定义一个对象 */
let sourceObj = {
age:20,
name:'yaodao',
boolean:true,
null:null,
undefined:undefined,
threeD:[95,96,97],
say:function(language){
console.log(language)
},
otherObj:{name:"chen",age:15}
}
/* 深拷贝 */
function deepCopy(sourceObj){
let targetObj = Array.isArray(sourceObj) ? [] : {};
let clone;
for(var key in sourceObj){
clone = sourceObj[key];
if(typeof(clone) === 'object'){
if (clone instanceof Object) {
/* 每一个对象成员都进行拷贝,数组也是如此 */
targetObj[key] = deepCopy(clone);
} else {
targetObj[key] = clone;
}
}else if (typeof(clone) === 'function') {
/* 对函数进行拷贝 */
targetObj[key] = eval(clone);
} else {
targetObj[key] = clone;
}// 对基本类型的拷贝
}
return targetObj;
}
let copyObj = deepCopy(sourceObj);
/* 修改对象的参数 */
copyObj.age = 25;
copyObj.name= 'yaodao';
copyObj.boolean = false;
copyObj.threeD[0] = 100;
copyObj.otherObj.name= 'chen';
copyObj.otherObj.age= 20;
copyObj.say('Chinese') // 具有继承的效果
console.log(copyObj)
console.log(sourceObj)
输出结果:
内存中的存在形式:
为了实现完整的功能,可以将深浅拷贝结合在一起:
如果 deep 为 true 表示进行深拷贝,为 false 表示进行浅拷贝。
function copy(deep /* [true || false] */, sourceObj) {
let targetObj = Array.isArray(sourceObj) ? [] : {};
let clone;
for (var key in sourceObj) {
clone = sourceObj[key];
if (deep && typeof(clone) === 'object') {
if (copy instanceof Object) {
targetObj[key] = copy(deep, copy);
} else {
targetObj[key] = clone;
}
} else if (deep && typeof(clone) === 'function') {
targetObj[key] = eval(clone.toString());
} else {
targetObj[key] = clone;
}
}
return targetObj;
}
4. 拷贝模板
(1)对象的深浅拷贝
对象与对象间的浅拷贝
/* 对象间的拷贝 */
var objA = {
name:'chen',
age:20,
say:function(){
console.log(1)
},
watch:function(){
console.log(2)
},
otherObj:{name:'yaodao',age:25}
// ...
};
var objB = {};
function copy(sObj,cObj){
var clone;
for(var key in sObj){
clone = sObj[key];
cObj[key] = clone;
}
}
copy(objA,objB);
对象与对象间的深拷贝 ----- 递归调用浅拷贝
上上述方法,此处省略…
对象间的深拷贝 ----- Object.create(),Object.assign()
说明:
1)Object.assign();存在兼容性问题
2)使用Object.create(),Object.assign()进行拷贝,若拷贝对象只有一级,则表示为深拷贝,若多级则表示为浅拷贝(从第二级开始)。
//*************************************** 深拷贝
/* Object.assign() */
/* 一级深拷贝 */
var sObj = {
name: 'chen',
age: 10
}
var cObj = Object.assign({}, sObj)
cObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10}
/* Object.create() */
/* 一级深拷贝 */
function create(sObj){
return Object.create(sObj);
}
var sObj = {
name: 'chen',
age: 10
}
var cObj = create(sObj)
cObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10}
//*************************************** 浅拷贝
/* Object.assign() */
/* 一级深拷贝 */
var sObj = {
name: 'chen',
age: 10,
/* 从第二级开始浅拷贝 */
otherObj:{name:'jack'}
}
var cObj = Object.assign({}, sObj)
cObj.otherObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10,otherObj:{name:'yaodao'}}
/* Object.create() */
/* 一级深拷贝 */
function create(sObj){
return Object.create(sObj);
}
var sObj = {
name: 'chen',
age: 10,
/* 从第二级开始浅拷贝 */
otherObj:{name:'jack'}
}
var cObj = create(sObj)
cObj.otherObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10,otherObj:{name:'yaodao'}}
对象间的深拷贝 ----- es6 对象解构
说明:
1)使用es6对象解构进行拷贝,若拷贝对象只有一级,则表示为深拷贝,若多级则表示为浅拷贝(从第二级开始)。
/* es6 对象解构 */
/* 一级深拷贝 */
var sObj = {
name: 'chen',
age: 10
}
var cObj = {...sObj}
cObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10}
/* 多级浅拷贝 */
var sObj = {
name: 'chen',
age: 10,
/* 从第二级开始浅拷贝 */
otherObj:{name:'jack'}
}
var cObj = {...sObj}
cObj.otherObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10,otherObj:{name:'yaodao'}}
(2)数组的深浅拷贝
纯数组的深拷贝 ----- concat(),slice()
说明:
1)纯数组表示由基本类型值组成的数组。
2)使用concat(),slice()对纯数组进行拷贝时会返回一个全新的数组,不会影响原始数组所以被用作深浅拷贝。
/* concat()进行纯数组深拷贝 */
let a = [1, '2'];
let b = [3, true];
let copy = a.concat(b);
a[1] = 5;
b[1] = 6;
// let copy = a.concat(b); 放到此处输出 [1, 5, 3, 6]
console.log(copy); //=> [1, "2", 3, true]
console.log(a); //=> [1, 5]
console.log(b); //=> [3, 6]
/* slice()进行纯数组深拷贝 */
let a = [1, '2'];
let b = a.slice();
a[0] = 0;
a[1] = 1;
b[0] = 10;
b[1] = 20;
console.log(a); //=> [0, 1]
console.log(b); //=> [10, 20]
混合数组的浅拷贝 ----- concat(),slice()
说明:
1)混合数组表示由基本类型值和引用类型值组成的数组。
2)使用concat(),slice()对混合数组进行拷贝时,基本类型深拷贝,引用类型浅拷贝,会影响原始数组。
/* concat()进行混合数组浅拷贝 */
let a = [1, {name: 'hh1'}];
let b = [2, {name: 'kk1'}];
let copy = a.concat(b);
copy[1].name = 'hh2';
copy[3].name = 'kk2';
console.log(copy); //=> [1, {name: 'hh2'}, 2, {name: 'kk2'}]
/* slice()进行混合数组浅拷贝 */
let a = [1, {name: 'hh1'}];
let copy = a.slice();
copy[0] = 2;
copy[1].name = 'hh2';
console.log(a); //=> [1, {name: 'hh2'}]
console.log(copy); //=> [2, {name: 'hh2'}]
数组的深拷贝 ----- es6 对象解构
说明:
1)使用es6数组解构进行拷贝,若拷贝数组是纯数组,则表示为深拷贝,若是混合数组则表示为浅拷贝。
/* es6 数组解构 */
/* 纯数组深拷贝 */
var arr = [1,2,3]
var res= [...arr]
res[0] = 10;
console.log(arr); //=> [1, 2, 3]
console.log(res); //=> [10, 2, 3]
/* 混合数组浅拷贝 */
var arr = [1,{name:'yaodao'},3,function say(){console.log(1)}]
var res= [...arr]
res[0] = 10;
res[1].name = 'chen';
console.log(arr); //=> [1, {name:'chen'}, 3, fn]
console.log(res); //=> [10, {name:'chen'}, 3,fn]
res[3]() //=> 1
(3)特殊深拷贝
JSON.parse()
说明:
1)用 JSON.stringify() 把对象转成字符串,再用 JSON.parse() 把字符串转成新的对象,可以实现对象,纯数组,混合数组的深复制。
2)缺点:function 没办法转成 JSON,也就是说使用此方法拷贝不了函数。
let sourceObj = {
age:20,
name:'yaodao',
boolean:true,
null:null,
undefined:undefined,
threeD:[95,96,97],
say:function(){
console.log(1);
},
otherObj:{name:"chen",age:15}
}
let copy = JSON.parse(JSON.stringify(sourceObj));
copy.age = 30;
copy.otherObj.age = 25;
console.log(sourceObj);
console.log(copy);
输出: