1.js组成
2.值类型和引用类型
在栈里面存的是地址(或者说 引用)- 参数传递时:值传递的是值 引用传递的是地址/引用
-
3.创建对象的三种方式
方式二、三 不能看出其原型。
1.工厂模式 自定义构造函数方式(√)
2.字面定义
3.调度系统的构造函数
4.构造函数与实例对象 原型对象
构造函数里面有个原型,原型里面的constructor指向构造函数
- 三者关系:
在原型中,
有propotype 就是构造函数,没有就是实例
- 创建方式:构造函数与在原型添加方法 实例 的方式:
- this指向问题
构造函数 原型对象的this指向实例
原型:想要实现数据共享,就写到原型里面。
好处:数据共享,节省内存空间。
而如果用上面 创建对象的三种方式 在实例创建两个实例,并调用函数时,所用的方法都不是同一个。而是两个不同的方法。实质是开辟了两个内存空间存放eat方法
例如:
1、把方法写在原型中比写在构造函数中消耗的内存更小,因为在内存中一个类的原型只有一个,写在原型中的行为可以被所有实例共享,实例化的时候并不会再实例中复制一份。
而写在类中的方法,实例化的时候会在每一个实例对象中在复制一份,所以消耗的内存更高。因此没有特殊原因,我们一般把属性定义在类中,行为定义在原型中。
原型链
原型链的改变
上图参考:https://www.jianshu.com/p/63c01996ec73
5.闭包与匿名函数
匿名函数
顾名思义就是没有名字的函数。立即执行。
// 这么写会报错,因为这是一个函数定义:
function() {}()
// 常见的(多了一对括号),调用匿名函数:
(function() {})()
// 但在前面加上一个布尔运算符(只多了一个感叹号),就是表达式了,将执行后面的代码,也就合法实现调用
!function() {}()
两种写法:
var aa = function(x){
alert(x);
}(5);//5
(function(x){alert(x);})(5);//5
参考:http://caibaojian.com/javascript-closure-fn.html
立即执行函数的作用只有一个:创建一个独立的作用域,在这个作用域里面,外面访问不到,避免变量污染。
for(var i=0;i<6;i++){
setTimeout(function(){
console.log(i); //为什么输出的总是 6,而不是0,1,2,3,4,5
},i*1000);
}
我们发现上面这个定时器总是输出6,因为setTimeout里面的执行函数是异步的,执行的时候,i的值是贯穿整个作用域的,而不是单独一个给每个定期器分配了一个i,for运行完的值是6,此时输出就总是6了。
那怎么解决呢?用立即执行函数给每个定时器创造一个独立作用域即可。
for(var i=0;i<6;i++){
(function(j){
setTimeout(function(){
console.log(j);
},j*1000);
})(i);
}
在for循环执行时,立即执行函数就已经有了结果了。而每个立即执行函数里面的j值就是独立的一个,不会受后面影响。所以会分别执行5次定时器。
//第一个立即执行函数
(function(0){
setTimeout(function(){
console.log(0);
})
})(0);
//第二个立即执行函数
(function(1){
setTimeout(function(){
console.log(1);
})
})(1);
//……
//第六个立即执行函数
(function(5){
setTimeout(function(){
console.log(5);
})
})(5);
i 的值从 0 变化到 5,对应 6 个立即执行函数,这 6 个立即执行函数里面的 j 「分别」是 0、1、2、3、4、5。
闭包
由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存;所以过度使用闭包会导致性能下降
f1() 输出10
输出 10
输出 10
闭包的两种实现
参考:https://www.bilibili.com/video/BV1Wb411H7Pj?p=31
- 将一个函数作为另一个函数的返回值:
- 将函数作为实参传递给另一个函数调用:
闭包+匿名函数
但上面:如果要实现:以下功能
普通闭包实现不了:
正确的打开方式:
ff此时是个函数
可以利用自执行函数和闭包来保存某个特殊状态中的值
继承
代码:见GitHub:https://github.com/sharryling/My-Practice
1.构造继承
2.原型链继承
3.原型式继承
4.拷贝方式
5.组合继承
6.class继承
/* 1.构造继承 apply call 推荐 */
/* 使用call和apply借用其他构造函数的成员, 可以解决给父构造函数传递参数的问题, 但是获取不到父构造函数原型上的成员.也不存在共享问题 */
function Person0(name, friends) {
this.name = name
this.friends = friends
this.showName = function () {
console.log(this.name);
}
}
function stu0(name, friends) {
Person0.apply(this, [name, friends])
this.eat = function () {
console.log("gogo:", this.friends);
}
}
console.log(" 1.构造继承");
var s0 = new stu0('aaa', 'ccc')
s0.showName()
s0.eat()
/* 2.原型链继承 */
/* 即 子构造函数.prototype = new 父构造函数() */
function Person(name, age) {
this.name = name
this.age = age
this.eat = function () {
console.log(this.age);
}
}
function std(name) {
this.name = name
this.eatI = function () {
console.log("hhh");
}
}
/* console.log(std.prototype); */
console.log(" 2.原型链继承");
std.prototype = new Person() //std -> person
std.prototype.constructor = std
/* 3.原型式继承 */
/* 借用构造函数的原型对象实现继承 */
function Person1(name, age) {
this.name = name
this.age = age
this.eat = function () {
console.log(this.age);
}
}
function std1(name) {
this.name = name
this.eat = function () {
console.log("hhh");
}
}
console.log(" 3.原型式继承");
std1.prototype = std.prototype //此时 std1 -> person
var objStd1 = new std1()
/* 4.拷贝方式 */
/* 就是将对象的成员复制一份给需要继承的对象 */
var Person2 = {
name: 'Li',
age: 25,
friends: ['小明', '小李', '小赵'],
showName: function () {
alert(this.name);
}
}
var std2 = {}
for (index in Person2) {
std2[index] = Person2[index]
}
Person2.age = 30
console.log(" 4.拷贝方式");
//console.log(Person2)
//console.log(std2)
/* class继承
/* 5.组合继承 */
/* 借用构造函数 + 原型式继承 */
function Person(name, age) {
this.name = name;
this.age = age;
this.showName = function () {
console.log(this.name);
}
}
Person.prototype.showAge = function () {
console.log(this.age);
}
function Student(name) {
Person.call(this, name);
}
console.log(" 5.组合继承");
Student.prototype = Person.prototype;
Student.prototype.constructor = Student;
/* 6.class继承 */
class animal {
constructor(name) {
this.name = name
}
hello() {
console.log("name = ",this.name)
}
}
console.log('class继承');
class Dog extends animal{
constructor(name,age){
super(name)
this.age = age
}
go(){
console.log("gogo");
}
}
console.log(" 6.class继承");
var xm = new Dog('xm','23')
xm.hello()
xm.go()
学习参考:https://www.jianshu.com/p/b76ddb68df0e、 http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html
promise
promise + http请求:
let myFirstPromise = new Promise((resolve, reject) => {
$.post('https://cloudapi.usr.cn/usrCloud/dev/getDevs',
JSON.stringify(param),
function (result) {
resolve(result)
},
);
})
myFirstPromise
.then((result)=>{
console.log("hahaaaaa1")
return myFirstPromise()
//注意一定要return,否则没有返回值,无法链式迭代后面的than
})
.catch((result)=>{
console.log("hahaaaaa2")
return myFirstPromise()
})
.then((result)=>{
console.log("hahaaaaa1")
return myFirstPromise()
})
不管Promise实现怎么复杂,但是它的用法却很简单,组织的代码很清晰,从此不用再受callback的折磨了。promise作为一个新的API,它的API本身没有什么特别的功能,但是它背后代表的编程思路是很有价值的。
最后,Promise是如此的优雅!但Promise也只是解决了回调的深层嵌套的问题,真正简化JavaScript异步编程的还是Generator,在Node.js端,建议考虑Generator。
apply call bind方法(为了改变函数体内部 this 的指向。)
参考:https://www.cnblogs.com/moqiutao/p/7371988.html
- apply、call
在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向。
JavaScript 的一大特点是,函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。
function fruits() {}
fruits.prototype = {
color: "red",
say: function() {
console.log("My color is " + this.color);
}
}
var apple = new fruits;
apple.say(); //My color is red
但是如果我们有一个对象banana= {color : “yellow”} ,我们不想对它重新定义 say 方法,那么我们可以通过 call 或 apply 用 apple 的 say 方法:
banana = {
color: "yellow"
}
apple.say.call(banana); //My color is yellow
apple.say.apply(banana); //My color is yellow
所以,可以看出 call 和 apply 是为了动态改变 this 而出现的,当一个 object 没有某个方法(本栗子中banana没有say方法),但是其他的有(本栗子中apple有say方法),我们可以借助call或apply用其它对象的方法来操作。
- bind
bind() 方法与 apply 和 call 很相似,也是可以改变函数体内 this 的指向。
MDN的解释是:bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
浅拷贝与深拷贝
参考:https://blog.csdn.net/qq_39207948/article/details/81067482
- 定义:
1.浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用
2.深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”。在改变新的数组(对象)的时候,不改变原数组(对象)
深拷贝方法–数组
- concat()
var array = [1, 2, 3, 4]; var copyArray = array.concat(); copyArray[0] = 100; console.log(array); // [1, 2, 3, 4] console.log(copyArray); // [100, 2, 3, 4]
- slice()
var array = [1, 2, 3, 4]; var copyArray = array.slice(); copyArray[0] = 100; console.log(array); // [1, 2, 3, 4] console.log(copyArray); // [100, 2, 3, 4]
slice() 方法返回一个从已有的数组中截取一部分元素片段组成的新数组(不改变原来的数组!)
用法:array.slice(start,end) start表示是起始元素的下标, end表示的是终止元素的下标
当slice()不带任何参数的时候,默认返回一个长度和原数组相同的新数组
[arr.splice(1,2):删除arr下标1的位置,并且返回删除的数组]
- 直接遍历 push
深拷贝方法–对象
- 直接遍历
- ES6的Object.assign
var obj = {
name: '彭湖湾',
job: '学生'
}
var copyObj = Object.assign({}, obj);
copyObj.name = '我才不叫彭湖湾呢! 哼 (。・`ω´・)';
console.log(obj); // {name: "彭湖湾", job: "学生"}
console.log(copyObj); // {name: "我才不叫彭湖湾呢! 哼 (。・`ω´・)", job: "学生"}
Object.assign:用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),并返回合并后的target
下面这一招可谓是“一招鲜,吃遍天”
JSON.parse(JSON.stringify(XXXX))
var array = [
{ number: 1 },
{ number: 2 },
{ number: 3 }
];
var copyArray = JSON.parse(JSON.stringify(array))
copyArray[0].number = 100;
console.log(array); // [{number: 1}, { number: 2 }, { number: 3 }]
console.log(copyArray); // [{number: 100}, { number: 2 }, { number: 3 }]