Javascript高级笔记
1. new创建对象的本质
function Person(name,age){
this.name = name;
this.age = age;
this.sayHi = function(){
return "Hello,I'm" + this.name;
}
}
let John = new Person(name,age);
/**
* new的过程经过了这几步
* 1. let obj = {};新建一个对象,开辟空间
* 2. obj.__proto__ = Person.prototype;将对象的__proto__指向构造函数的prototype
* 3. Person.call(obj); 执行构造函数中的代码,注意。此时的this为obj
* 4. 最后返回obj
*
*/
2. 几种常见的对象创建方式
1. 字面量创建方式
let person = {
name: "孙悟空",
age: 100,
sex: 1,
power: 10000,
skill: [{
name: "龟派气功",
hurt: 10000,
},
{
name: "十倍界王拳",
hurt: 5000,
}, {
name: "元气弹",
hurt: 999999,
}
]
};
person.skill.map(item => {
console.log(item.name + "," + item.hurt);
});
2. 系统构造函数
let obj = new Object();
obj.name = "John";
obj.age = 18;
obj.sayhi = function(){
console.log("Hello,I'm John");
}
以上两种方式每次只能创建一个实例对象,当要创建多个相似对象时,存在代码冗余问题
3. 工厂函数创建对象
//可以传参数,提高代码复用,
function createStudent(name,age){
let student = new Object();
student.name = name;
student.age = age;
student.sayhi = function(){
console.log("Hello,I'm "+student.name);
}
return student;
}
//但是无法判读属于哪个对象。(instanceof)
4. 自定义构造函数
/**
* 自定义构造函数可以用instanceof判断属于哪个对象,且解决了代码复用问题,但是每个公共方法都会创建一个实例对象,造成内存浪费。
*/
function Student(name,age){
this.name = name;
this.age = age;
this.sayHi = function(){
console.log("Hello,I'm " + this.name);
}
}
5. 混合模式(构造函数模式+原型模式)
/**
* 原型的作用,共享数据,节省内存,不同实例对象的sayHi方法都指向同一个
*/
function Student(name,age){
this.name = name;
this.age = age;
}
Student.prototype = {
//将自身的prototype指向构造函数
constructor : Student,
sayHi:function(){
console.log("Hello,I'm" + this.name);
}
}
3. 原型链
1. 构造函数的原型对象prototype
每个函数function Person()
为例,都有一个原型对象prototype,该原型对象默认存在两个属性,一个是constructor
指向他的构造函数,即Person()
,还有一个__ proto __,指向的是实例化Person的够构造函数的prototype,即Function.prototype
,因为function Person()
相当于 let Person = new Function()
。
function Person(age) {
this.age = age;
}
let per = new Person(18);
console.dir(Person);
console.log(per.__proto__ === Person.prototype);//true
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yp59aLXu-1585295137728)(/Users/mac/Desktop/web/js高级/assets/image/prototype.png)]
2. 实例对象的原型对象__ proto __
首先需要清除new的过程,因为实例化过程就是new Person(),需要清楚new这个过程发生的事情
* new的过程经过了这几步
* 1. let obj = {};新建一个对象,开辟空间
* 2. obj.__proto__ = Person.prototype;将对象的__proto__指向构造函数的prototype
* 3. Person.call(obj); 执行构造函数中的代码,注意。此时的this为obj
* 4. 最后返回obj
在new的过程中,obj.__ proto__ 指向构造函数的prototype,所以每个实例对象都存在一个__ __ proto __属性,而这个属性就是指向构造函数的prototype对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmMdC8F0-1585295137730)(/Users/mac/Desktop/web/js高级/assets/image/原型链.png)]
3.原型链的构成
小总结:
1.每个函数都存在prototype和__ proto __ 。每个实例对象都存在__ proto __ 。
2.原型链就是实例对象的__ proto __ 指向构造函数的prototype ,构造函数的prtotype.__ proto **指向Object.prototype,最后的Object.prototype. proto __指向null**,而只要是在这条链上的属性或方法,
实例对象都能访问
在js中,只要是在原型链(prototype)上的属性,在哪个环节都可以任意的调用,例子↓
//构造函数
function Person(name){
this.name = name;
}
//在构造函数原型对象上添加方法
Person.prototype.run = function(){
console.log("跑步");
}
//在Function上定义一个方法
Object.prototype.printfName = function(){
console.log(this.name);
}
//实例化对象
let per = new Person("GZH");
//此时per可以调用run()方法
per.run();//跑步
//也可以调用printfName方法,因为prototype是new Object(),也就是prototype是Object的实例对象,所以prototype中的__proto__指向Object的prototype,所以printfName也在per的原型链上,可以访问
per.printfName();//GZH
4. 原型对象的指向是可以被改变的
因为prototype也是一个对象,所以他也可以指向任何对象,这也是继承的重要基础。
function Person(name) {
this.name = name;
}
Person.prototype.run = function(){
console.log("跑步");
}
function Student() {
}
Student.prototype.study = function(){
console.log("学习");
}
let stu1 = new Student();
//可以访问study,因为study在原型链上
stu1.study();//学习
//改变Student的指向,此时Student.prototype = {age,__proto__(指向Person的prototype)}
Student.prorotype = new Person();
let stu2 = new Student();
stu2.run();//可以访问,因为run()方法在原型链上
stu2.study();//报错,因为原型链上已经发生改变
4. 继承
1. 原型链继承
//父类 人类 --- 姓名,年龄,性别, -行为- 吃,玩
//子类 学生 --- 姓名,年龄,性别, -行为- 吃,玩,学习
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex || 1;
}
Person.prototype.eat = function () {
console.log(this.name + "吃东西");
}
Person.prototype.play = function () {
console.log(this.name + "玩")
}
function Student(sorce) {
this.sorce = sorce;
}
Student.prototype = new Person("小明", 18, 1);
Student.prototype.study = function () {
console.log(this.name + "在学习");
}
let stu = new Student("100");
console.dir(stu);
stu.study();
优点:优点是继承了父类的模板,又继承了父类的原型对象
缺点:对象属性固定,修改起来麻烦;例如连续创建几个学生实例对象,名字都是相同,要改则需要用stu.name = “…”,这样修改。
2. 借用构造函数修改
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.eat = function () {
console.log("吃饭饭");
};
function Student(name, age, sex, score) {
Person.call(this, name, age, sex);//利用call的方法执行Person构造函数,初始化属性
this.score = score;
}
let stu = new Student("小明", 18, "男", 100);
stu.eat();
优点:比原型链继承更加灵活,实例对象时即可初始化自身属性
缺点:无法访问父类的prototype方法
3.组合继承
// 借用组合继承(原型继承+借用构造函数继承)
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.eat = function () {
console.log("吃饭饭");
};
function Student(name, age, sex, score) {
Person.call(this, name, age, sex);
this.score = score;
}
Student.prototype = new Person();//改变原型指向
Student.prototype.constructor = Student;//将构造函数指向本身
let stu = new Student("小明", 18, "男", 100);
stu.eat();
优点:结合原型链继承和借用构造函数继承,就完美地解决了之前这二者各自表现出来的缺点。即可继承属性,又可继承方法
缺点:Person()方法执行了两次,一次 Person.call(this, name, age, sex);一次new Person();
4.拷贝继承
//1.赋值,无开辟新空间
let obj = {
name:"Gzh",
age:18
}
let obj1 = obj;
//2.开辟了新空间
function Person() {
}
Person.prototype.name = "阿萨德";
Person.prototype.age = 18;
Person.prototype.sex = "男";
Person.prototype.sayHi = function () {
console.log("hello");
};
let student = {};
for (let key in Person.prototype) {
student[key] = Person.prototype[key];
}
优点:支持多继承
缺点:1、效率较低,内存占用高(因为要拷贝父类的属性)
2、无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
5. 寄生组合继承
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.eat = function () {
console.log(this.name + "在吃饭");
}
function Student(name, age, sex, grade) {
Person.call(this, name, age, sex);
this.grade = grade;
}
(function () {
function Super() {};
Super.prototype = Person.prototype;
console.log(Super);
Student.prototype = new Super();
})();
let stu = new Student("wassupbro", 18, "男", "大二");
stu.eat();//wassupbro在吃饭
console.dir(stu);
优点:既可以继承属性,又可以继承方法,也只执行了一次Person的构造函数,堪称完美
缺点:实现起来稍复杂
5.call、apply、bind方法
1. js中this指向问题
1. 在全局环境中
在全局执行环境中(在任何函数体外部),this都是指向全局对象。在浏览器中,window对象即是全局对象;
console.log(this); //Window
var a = 1;
console.log(window.a); //1
2.在函数环境中
在函数内容,this指向取决于函数调用的方式;
function f(){
"use strict"; //使用严格模式
console.log(this); } f(); // window ;使用严格模式时,输出undefined
3. 作为对象的方法调用时
当函数作为对象的方法被调用时,this指向调用的该函数的对象;
var obj = {
a: 37,
fn: function() {
return this.a;
}
};
console.log(obj.fn()); // 37
4.作为构造函数
当一个函数用作构造函数时(使用new关键字),它的this
被绑定到正在构造的新对象。
虽然构造器返回的默认值是this所指的那个对象,但它仍可以手动返回其他的对象(如果返回值不是一个对象,则返回this对象)。
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // 37
function C2(){
this.a = 37;
return {a:38};
}
o = new C2();
console.log(o.a); // 38,手动设置了返回对象
5.作为DOM事件处理函数
当函数被用作事件处理函数时,它的this
指向触发事件的元素(一些浏览器在使用非addEventListener
的函数动态添加监听函数时不遵守这个约定)。
2. call和apply方法
- 作用:用来改变函数中this的指向
- 用法:Person.call(对象,参数1,参数2,…);Person.apply(对象,[参数1,参数2,…])
- 使用:需要调用其他对象的方法,并且this指向自身时;
- 实例,下面的例子↓
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.eat = function () {
console.log(this.name + "在吃饭");
};
let obj = {
name: "gzh",
age: 18
};
let per = new Person("htat", 18);
per.eat(); //htat在吃饭
per.eat.call(obj); //gzh在吃饭,改变了this的指向
3.bind方法
-
作用:改变this指向,复制一份到变量
-
实例
let obj = { name: "gzh", age: 18, eat: function () { console.log(this.name); } }; let obj2 = { name: "http" }; obj2.eat = obj.eat.bind(obj2); //改变了this的指向并将eat方法复制给obj2.eat obj2.eat();//http
6.闭包
-
概念:函数A内的数据可以被函数B访问
-
模式:函数闭包,对象模式的闭包
-
作用:缓存数据,延长作用域;私有化变量
-
缺点:可能会造成数据泄露
//例子:
//函数模式闭包
function f3() {
let num = 10;
return function () {
return ++num;
};
}
let f4 = f3();
console.log(f4());//11 num没有被系统回收
console.log(f4());//12
console.log(f4());//13
//对象模式闭包
function f2() {
let num = 20;
let obj = {
age: num,
};
return obj;
}
console.log(f2().age);//函数外可以访问到f2中的变量,
利用闭包实现一个点赞的案例
<input type="button" value="点赞(1)">
<input type="button" value="点赞(1)">
<input type="button" value="点赞(1)">
<input type="button" value="点赞(1)">
<script>
let btnArr = document.getElementsByTagName("input");
for (let i = 0; i < btnArr.length; i++) {
let value = 1;
btnArr[i].onclick = function () {
this.value = "点赞(" + (++value) + ")";//注册点击事件,可以访问到value
}
}
</script>
7. 沙箱
概念:沙箱 就是形成一个密闭的环境 不能 主动的影响外界,在js中就是自执行函数
作用:新建一块可用于模块运行的环境(沙箱),自己的代码放在回调函数里,且不会对其他的个人沙箱造成影响
let num = 1000;
((function(){
//在自执行函数中有自己的作用域
let num = 10;
console.log(num);//10
})())
console.log(num);//1000
8. 浅拷贝和深拷贝
js中有基本类型(Number,String,Null,Undefined,Boolean,Symbol)和引用类型(Object,Array,Function),基本类型的赋值会复制值,而引用类型的赋值则会复制他的引用地址。
1.浅拷贝
概念:将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用
实现方法:
-
遍历复制
let obj2 = { name: "gzh", age: 18, hobby: ["study", "basketball", "football"], firend: [{ name: "asd", age: 18 }] }; let obj3 = {}; for (let key in obj2) { obj3[key] = obj2[key]; } obj3.name = "hha"; obj3.hobby[0] = "run"; console.log(obj2.name);//gzh console.log(obj2.hobby[0]);//run,只做第一层拷贝,obj3.hobby跟obj2.hobby指向同一个引用
-
ES6的Object.assign()
let obj4 = Object.assign(); obj4.name = "hha"; obj4.hobby[0] = "run"; console.log(obj2.name);//gzh console.log(obj2.hobby[0]);//run,只做第一层拷贝,obj3.hobby跟obj2.hobby指向同一个引用
-
ES6的扩展运算符
let obj5 = {...obj2}; obj5.name = "hha"; obj5.hobby[0] = "run"; console.log(obj2.name);//gzh console.log(obj2.hobby[0]);//run,只做第一层拷贝,obj3.hobby跟obj2.hobby指向同一个引用
2.深拷贝
概念:深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝
实现方法:
-
JSON.parse()和JSON.stringify()
let obj6 = JSON.parse(JSON.stringify(obj2)); obj6.name = "hha"; obj6.hobby[0] = "run"; console.log(obj2.name);//gzh console.log(obj2.hobby[0]);//study 深拷贝,两个对象互不影响
缺点:当值为
undefined
、function
、symbol
会在转换过程中被忽略。。。 -
自己封装函数
function extend(a, b) { for (let key in a) { let item = a[key]; if (item instanceof Array) { b[key] = []; extend(item, b[key]); } else if (item instanceof Object) { b[key] = {}; extend(item, b[key]); } else { b[key] = item; } } } let obj7 = extend(obj2,obj7); obj7.name = "hha"; obj7.hobby[0] = "run"; console.log(obj2.name);//gzh console.log(obj2.hobby[0]);//study 深拷贝,两个对象互不影响
-
immutable.js库
下次继续更新,个人博客zehongguo.github.io