前端学习日志-4-js
js
- 基本数据类型(6)
- null
- undefined
- boolean
- number
- 复杂数据类型:object
- 对象创建
- 继承
- 操作
- 定义属性
- 读写属性
- 删除属性
- 合并对象
- 判断类型
- typeof
- instanceof
- Object.prototype.toString.call()
- 隐式转换
- 判断两个对象是否相等
- Arrary
- 方法
- var、let、const
- var
- let
- const
- 变量提升、函数提升、提升优先级
- 变量提升
- 函数提升
- 函数变量同名
- 隐式全局变量不提升
- 函数
- 构造函数
- 原型链
- new过程
- 嵌套函数
- 闭包
- 回调函数
- 箭头函数
- 综合案例
- this
- 事件循环
- 任务队列
- 异步
- Promise
- SetTimeout
- 例子
- ajax
注释 单行 //内容 多行 /*内容*/
基本数据类型(6)
null、undefined、number、string、boolean、symbol(ES6后新增)
通过字面量的方式创建:var a = ‘string’;,这时它就是基本类型值;通过构造函数的方式创建:var a = new String(‘string’);这时它是对象类型。
基本类型是没有属性和方法的,但仍然可以使用对象才有的属性方法。这时因为在对基本类型使用属性方法的时候,后台会隐式的创建这个基本类型的对象,之后再销毁这个对象
null
undefined
boolean
数据类型 | if判断为true | if判断为false |
---|---|---|
string | 任何非空字符串,空格也算 | ‘’(空字符串 |
number | 任何非0数字、Infinity | 0、NaN |
object | 任何对象 | null |
undefined | 不适用 | undefined |
null==undefined、null==null、undefined==undefined都为true
number
1.浮点数、整数
Number()
parseInt()
parseFloat()
2.NaN
1、表示不是数字,但是其实它是一个特殊的数字(NaN:Not a Number)
2、当运算操作符错误的时候,一般会得到NaN
3、NaN具有传染性,即NaN参与任何运算,结果都为NaN
4、NaN与任何数值都不相等,NaN == NaN :falseisNaN()
用来检测当前这个值是否是有效数字,如果不是有效数字,检测的结果是true;反之为false
isNaN(0)->false
isNaN(NaN)->true
isNaN(‘12’)–>false 当我们使用isNaN检测值的时候,检测的值不是number类型的,浏览器会默认的把值转化为number类型,然后再去检测
3.Infinity
1、Infinity:数据超过了JS可以表示的范围,是一个特殊的数字
2、Infinity与其他数据类型进行操作运算,结果还是Infinity
3、数字除以0得到Infinity
4、Infinity+或*Infinity为Infinity,Infinity-或/或%Infinity为NaNisFinite()
如果是NaN或者Infinity返回false,否则返回true
isFinite('12')->true
复杂数据类型:object
引用类型可细分为:Object、Array、Date、Function、RegExp
对象创建
1.直接json创建
var student = {name: "Tom",age: 22,sex: "boy";setName: function () { }
}
2.new+ Object
var student = new Object();
student.name = "Tom";
student.age = 22;
student.sex = "boy";
student.setName = function () { }
这两种方法同一接口创建多个对象会产生大量重复代码
3.函数创建(工厂模式)
特点:大规模创建同类实例
缺点:无法区分该对象的类型,分别存放浪费内存
function createStudent(name, age, sex) {var s = new Object();s.name = name;s.age = age;s.sex = sex;s.setName = function () { }return s;
}
var s1 = createStudent("Jack", 20, "boy");
var s2 = createStudent("Tom", 22, "boy");
console.log(s1, s2);
console.log(s1 instanceof createStudent);//false
console.log(s1 instanceof Object);//true
4.new+ 构造函数
特点:识别对象
缺点:浪费内存
function Student(name, age, sex) {this.name = name;this.age = age;this.sex = sex;this.setName = function () { }
}
var s1 = new Student("Jack", 20, "boy");
var s2 = new Student("Tom", 22, "boy");
console.log(s1 instanceof Object);//true
console.log(s1 instanceof Student);//true
5.原型模式
特点:所有实例对象共享原型对象的属性方法,并可以覆盖同名属性方法,实现私有
function Student(name, age, sex) {Student.prototype.name = "Tom";Student.prototype.age = 20;Student.prototype.sex = "boy";Student.prototype.setName = function () { }
}
var s1 = new Student();
var s2 = new Student();
s1.name = "Jack";
console.log(s1, s2);
6.构造函数原型模式组合
function Student(name, age, sex) {this.name = name;this.age = age;this.sex = sex;
}
Student.prototype = {constructor=Student,setName=function () { }
}
var s1 = new Student("Jack", 20, "boy");
继承
1.原型链继承:子类原型为父类的一个实例对象
特点:
实例是子类的实例,也是父类的实例
父类新增原型方法 / 原型属性,子类都能【访问】到
简单,易于实现
缺点:
无法实现多继承
来自原型对象的所有属性被所有实例共享
创建子类实例时,无法向父类构造函数传参
要想为子类新增原型属性和方法,必须要在Student.prototype = new Person() 之后执行,不能放到
构造器中,实例属性方法可以放在构造器//父类型
function Person(name, age) {this.name = name;this.age = age;this.play = [1, 2, 3];this.setName = function () { }
}
Person.prototype.setAge = function () { }
//子类型
function Student(price) {this.price = price;this.setScore = function () { }
}
//子类型的原型为父类型的一个实例对象
Student.prototype = new Person("Visky",28);//不传参也可以,我只是为了显著分别才传参
//因为会改变原型的指向,所以应该放到重新指定之后
Student.prototype.sex = "boy";//Person实例对象Visky增加sex="boy"
Student.prototype.setsex = function () { }//Person实例对象Visky增加setsex=function(){}
var s1 = new Student(100);//Student通过原型链访问Person函数实例对象
var s2 = new Student(90);//Student通过原型链访问Person函数实例对象
var p1 = new Person("Lily",16);//Person不同的实例对象Lily
console.log(p1.sex);//undefined
console.log(s1.name, s2.name);//Visky Visky
s1.play.push(4);//s1没有play属性,修改的是原型链,所以都能读取
console.log(s1.play, s2.play);//都[1,2,3,4]
console.log(s1 instanceof Person); // true
console.log(s1 instanceof Student); // true
2.借用构造函数继承:子类构造函数通过call()调用父类型构造函数
特点:
解决了原型链继承中子类实例共享父类属性的问题
创建子类实例时,可以向父类传递参数
可以实现多继承(call多个父类对象)
缺点:
实例并不是父类的实例,只是子类的实例
只能继承父类的实例属性和方法,不能访问原型属性和方法
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能function Person(name, age) {this.name = name;this.age = age;this.play = [1, 2, 3];this.setAge = function () {console.log("setAge") }
}
Person.prototype.setName = function () { }
function Student(name, age, price) {Person.call(this, name, age);//每个子类都有不同的父类实例对象//相当于:this.Person(name,age);this.price = price;this.setScore = function () { }
}
var s1 = new Student("Tom", 20, 100);
var s2 = new Student("Jerry", 18, 90);
console.log(s1.name, s2.name);//Tom Jerry
s1.setName();//Uncaught TypeError: s1.setName is not a function
s1.setAge();//setAge
s1.play.push(4);
console.log(s1.play, s2.play);//[1, 2, 3, 4] [1, 2, 3]
console.log(s1 instanceof Person); // false
console.log(s1 instanceof Student); // true
3.原型链 + 借用构造函数组合继承
特点:
可以继承实例属性 / 方法,也可以访问原型属性 / 方法
不存在属性共享问题
可传参
函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例function Person(name, age) {this.name = name;this.age = age;this.play = [1, 2, 3]this.setName = function () { }
}
Person.prototype.setAge = function () {console.log("setAge")}
function Student(name, age, price) {Person.call(this, name, age);//继承实例方法属性this.price = price;this.setScore = function () { }
}
Student.prototype = new Person();//访问原型方法属性
Student.prototype.constructor = Student;//修复构造函数指向
Student.prototype.sayHello = function () {console.log("Hello")}
var s1 = new Student("Tom", 20, 100);
var s2 = new Student("Jerrry", 18, 90);
console.log(s1.name, s2.name);//Tom Jerrry
s1.sayHello();//Hello
s1.setAge();//setAge
s1.play.push(4);
console.log(s1.play, s2.play);//[1,2,3,4] [1,2,3]
console.log(s1 instanceof Person); // true
console.log(s1 instanceof Student); // true
console.log(s1.constructor) //Student函数
3.1组成继承优化1:子类原型对象指向父类原型对象
特点:
不会初始化两次实例方法 / 属性,避免的组合继承的缺点
缺点:
没办法辨别是实例是子类还是父类创造的,子类和父类的构造函数指向是同一个function Person(name, age) {this.name = name;this.age = age;this.play = [1, 2, 3]this.setName = function () { }
}
Person.prototype.setAge = function () {console.log("setAge")}
function Student(name, age, price) {Person.call(this, name, age);//继承实例方法属性this.price = price;this.setScore = function () { }
}
Student.prototype = Person.prototype;//优化点,不需修正构造函数指向
Student.prototype.sayHello = function () {console.log("Hello")}
var p1 = new Person("Peter", 22);
var s1 = new Student("Tom", 20, 100);
var s2 = new Student("Jerrry", 18, 90);
console.log(p1.name, s1.name, s2.name);
p1.setAge();//setAge
p1.sayHello();//Hello
s1.setAge();//setAge
s1.sayHello();//Hello
s1.play.push(4);
console.log(p1.play, s1.play, s2.play);//[1,2,3] [1,2,3,4] [1,2,3]
console.log(p1 instanceof Person, p1 instanceof Student);//true,true父类实例来自子类判断为真
console.log(s1 instanceof Person); // true
console.log(s1 instanceof Student); // true
console.log(s1.constructor) //Person函数
3.2组合继承优化2:原型可以基于已有的对象来创建对象,var B = Object.create(A)以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
function Person(name, age) {this.name = name;this.age = age;this.play = [1, 2, 3]this.setName = function () { }
}
Person.prototype.setAge = function () { }
function Student(name, age, price) {Person.call(this, name, age);//继承实例方法属性this.price = price;this.setScore = function () { }
}
Student.prototype = Object.create(Person.prototype);//优化点,Student继承Person的实例属性函数,可以访问原型属性函数
Student.prototype.constructor = Student;//修正构造函数指向
Student.prototype.satHello = function () { }
var p1 = new Person("Peter", 22);
p1.__proto__.money=1000;
var s1 = new Student("Tom", 20, 100);
var s2 = new Student("Jerrry", 18, 90);
console.log(p1.money, s1.money, s2.money);//1000 1000 1000
s1.play.push(4);
console.log(s1.play, s2.play);//无属性共享
console.log(p1 instanceof Person, p1 instanceof Student);//true,false
console.log(s1 instanceof Person); // true
console.log(s1 instanceof Student); // true
console.log(s1.constructor) //Student
操作
定义属性
1.创建时直接定义
const obj={age:18,
name:'tom'
}
2.点语法添加
const obj={}
obj.age=18
obj.name='tom'
3.Object.defineProperty(obj, prop, descriptor)
configurable
当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable
当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false。
const object1 = {};Object.defineProperty(object1, 'property1', {value: 42,writable: false
});object1.property1 = 77;
// throws an error in strict modeconsole.log(object1.property1);
// expected output: 42
读写属性
1.点语法
var obj = { //定义对象x : 1
}
console.log(obj.x); //访问对象属性x,返回1
obj.x = 2; //重写属性值
console.log(obj.x); //访问对象属性x,返回2
2.中括号语法
在中括号语法中,必须以字符串形式指定属性名,不能使用标识符。
中括号内可以使用字符串,也可以使用字符型表达式,即只要表达式的值为字符串即可。
obj.x1=4
console.log(obj["x"+1]; //4
console.log(obj["x"]); //2
obj["x"] = 3; //重写属性值
console.log(obj["x"]); //3
3.for in
obj={x:4,y:5,z:6}
for(let k in obj){console.log(obj[k])
}
//4 5 6
4.Object.getOwnPropertyNames
obj={x:4,y:5,z:6}
console.log(Object.getOwnPropertyNames(obj))
//["x", "y", "z"]
5.Object.keys和Object.values
obj={x:4,y:5,z:6}
Object.keys(obj)
//["x", "y", "z"]
Object.values(obj)
//[4, 5, 6]
删除属性
const obj = {x : 1}; //定义对象
delete obj.x; //删除对象的属性x
console.log(obj.x); //返回undefined
合并对象
1.Object.assign()
const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { a: 3 };const obj = Object.assign(o1, o2);
console.log(obj); // { a: 1, b: 2}
console.log(o1); // { a: 1, b: 2}, 注意目标对象自身也会改变。
console.info(o2); // { b: 2 }
const obj1 = Object.assign(o1, o3);
console.log(obj1);// { a: 3, b: 2}
console.log(o1); // { a: 3, b: 2}
console.info(o3); // { a: 3 }
2.for in
const o1 = {a: 1, d: 4}
const o2 = {b: 2, a: 3}
function assignObj(o1,o2){for(let k in o2){if(o2.hasOwnProperty(k)){if(!o1.hasOwnProperty(k)){o1[k]=o2[k]
}
else{o1[k]=[o1[k],o2[k]]
}
}
}
}
assignObj(o1,o2)
console.log(o1) //{a: [1, 3], d: 4, b: 2}
判断类型
typeof
typeof运算符的返回类型为字符串
返回值 | 类型 |
---|---|
‘boolean’ | 布尔类型的变量或值 |
‘undefined’ | 未定义的变量或值 |
‘string’ | 字符串类型的变量或值 |
‘number’ | 数字类型的变量或值 |
‘object’ | 对象类型的变量或值,或者null |
‘function’ | 函数类型的变量或值 |
instanceof
检测构造函数的 prototype 属性是否出现在某个实例对象的原型链
var a={};
a instanceof Object //true
a instanceof Array //false
var b=[];
b instanceof Array //true
b instanceof Object //true
Object.prototype.toString.call()
Object.prototype.toString.call(123)
//"[object Number]"Object.prototype.toString.call('str')
//"[object String]"Object.prototype.toString.call(true)
//"[object Boolean]"Object.prototype.toString.call({})
//"[object Object]"Object.prototype.toString.call([])
//"[object Array]"
Object.prototype.toString.call(null)
//"[object Null]"
隐式转换
==
数字==字符串,字符串转为数字比较
布尔值==?,都转为数字
对象==数字/字符串,先尝试valueOf()再尝试toString()转为基本值
1=='2' //false
1=='1' //true
1=='a' //false
true=='a' //false
true=='1' //true
true==1 //true
true==[1] //true
true==[0] //false
[1]==1 //true
[1]=='1' //true
[]
[]转为String是""
[]转为Number是0
[]转为Boolean是true
判断两个对象是否相等
Arrary
方法
- 不改变原数组
concat() :
slice() :
map() : - 改变原数组
push() :
pop() :
shift() :
unshifit() :
sort() :
splice() :
reverse() :
var、let、const
var
如果使用关键字 var 声明一个变量,那么这个变量就属于当前的函数作用域,如果声明是发生在任何函数外的顶层声明,那么这个变量就属于全局作用域
var a=1
function fun(){var a=2
console.log(a)
}
fun() //2
console.log(a) //1如果在声明变量时,省略 var 的话,该变量就会变成全局变量,如全局作用域中存在该变量,就会更新其值
function fun(){a=2
}
fun()
console.log(a) //2var a=1
function a(){a=2
}
fun()
console.log(a) //2var的变量复制,复制的 基本类型值 相互独立,栈内存会开辟一个空间保存新的变量和变量值,所以改变新变量的值对旧变量没有影响
var a=1
var b=a
b=2
console.log(a) //1
复制的 引用类型值 直接用=号赋值属于浅拷贝,只会拷贝对象的指针,实际指向的是堆内存中的同一object,所以改变新值时旧值会同步改变(改变任何一个变量的值,另外一个变量的值也会发生改变)
var a=[1,2]
var b=a
a.push(3) //b.push(3)
console.log(a,b) //[1,2,3] [1,2,3]var a=[1,2]
var b=a
a=[3,4] //重新赋值,不是修改
console.log(a,b) //[3,4] [1,2]
let
let声明的变量具有块作用域的特征,由{}包括起来,if语句和for语句里面的{}也属于块级作用域
for(let i=0;i<5;i++){setTimeout(function(){console.log(i);
},0)
} //0 1 2 3 4
对比var
for(var i=0;i<5;i++){setTimeout(function(){console.log(i);
},0)
} //5 5 5 5 5在同一个块级作用域,不能重复声明变量。
let i=1
function fun(){let i=2
let i=3 //Uncaught SyntaxError: Identifier 'i' has already been declared
}let i=1
function fun(){let i=2
console.log(i)
}
fun() //2
console.log(i) //1let声明的变量不存在变量提升,换一种说法,就是 let 声明存在暂时性死区(TDZ)。
let a=1
console.log(a) //1
console.log(b) //Uncaught ReferenceError: b is not defined
let b=2let复制同var
const
const声明基本数据类型:值保存在变量指向的那个内存地址,因此等同于常量,而且const声明的变量必须经过初始化
const a=1
a=2 //Uncaught TypeError: Assignment to constant variable
const b //Uncaught SyntaxError: Missing initializer in const declaration复合类型const变量保存的是引用。因为复合类型的常量不指向数据,而是指向数据所在的地址,所以通过const声明的复合类型只能保证其地址引用不变,但不能保证其数据不变
arr指向的地址不变
const arr=[]
arr.push(1)
arr[2]=3
console.log(arr) //[1, empty, 3][1,2]和[3,4]的地址不同
const arr=[1,2]
arr=[3,4] //Uncaught SyntaxError: Identifier 'arr' has already been declaredconst变量不会提升
consple.log(a) //Uncaught ReferenceError: consple is not defined
const a=10const和let作用域一致
const i=1
function fun(){const i=2
console.log(i)
}
fun() //2
console.log(i) //1
变量提升、函数提升、提升优先级
变量提升
console.log(a) //undefined,如果没有定义会报错 a is not defined
var a=10
console.log(a) //10
由于在js中代码执行的顺序是从上而下,所以在第一次打印的时候,要找变量a,如果没有变量提升的话应该会报错说a没有定义,而这里输出的是undefined,说明a已经定义了,但是没有赋值。那么这就是变量提升起了作用了。
函数提升
var a=10
fun() //10
function fun(){console.log(a)
}
函数变量同名
console.log(a) //ƒ a(){}
var a =10
function a(){}
console.log(a) //10js解析:
var a
function a(){}
console.log(a)
a=10
console.log(a)
function foo() {console.log(a); //a(){}var a = 1;console.log(a); //1function a() {}console.log(a); //1console.log(b); //b(){}var b = 2;console.log(b); //2function b() {}console.log(b); //2
}
foo()js解析:
function foo() {var a;var b;function a() {}function b() {}console.log(a); //a(){}a = 1;console.log(a); //1console.log(a); //1console.log(b); //b(){}b = 2;console.log(b); //2console.log(b); //2
}
foo();
隐式全局变量不提升
function foo() {console.log(a);console.log(b); // 报错未定义b = 'aaa';var a = 'bbb';console.log(a);console.log(b);
}
foo();
函数
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面
function log(x, y = 'World') {console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
log() //undefined "World"function Point(x = 0, y = 0) {this.x = x;this.y = y;
}
const p = new Point();
p // { x: 0, y: 0 }参数变量是默认声明的,所以函数代码块内不能用let或const再次声明。使用参数默认值时,函数不能有同名参数
function foo(x){let x=1; // errorconst x=2; //error//要注意var、let、const声明同名变量本身就会报错
}
function foo(x = 5) {let x = 1; // errorconst x = 2; // error
}
function foo(x, x, y) {// ...
} // 不报错
function foo(x, x, y = 1) {// ...
} // 报错 SyntaxError: Duplicate parameter name not allowed in this context
对象的解构赋值默认值和函数参数是不一样的
只使用了对象的解构赋值默认值,没有使用函数参数的默认值,那么调用的时候一定要有参数,没有参数则会报错。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况
function foo({x, y = 5}) {console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefinedfunction foo({x, y = 5} = {}) {console.log(x, y);
}
foo() // undefined 5 如果没有参数,那么是函数默认参数即空对象,然后解构赋值。function fetch(url, { body = '', method = 'GET', headers = {} }) {console.log(method);
}
fetch('http://example.com', {}) // "GET"
fetch('http://example.com') // 报错,第二个参数没有默认值function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {console.log(method);
}
fetch('http://example.com') // "GET"有,第二个参数具有默认值,是一个空对象,然后进行解构赋值。
1.对象参数才会具有双重默认参数
2.对象参数未定义默认函数参数,当函数调用无参数的时候会报错,找不到函数参数。
3.关键看函数调用:当函数调用具有参数的时候,默认函数参数无作用,解构参数有作用,用来补足剩余对象里面的参数默认值;当函数调用无参数的时候,默认函数参数和解构参数均具有作用,默认函数参数先生效,然后才是默认解构参数生效(补充作用,有则不用,无则补充)
// 写法一
function m1({x = 0, y = 0} = {}) {return [x, y];
}
// 写法二
function m2({x, y} = { x: 0, y: 0 }) {return [x, y];
}
//写法三
function m3({x=2, y=4} = { x: 0}) {return [x, y];
}
// 函数没有参数的情况,先是默认函数参数生效,然后是默认解构参数生效!
console.log('无参数情况', m1()) // [0, 0]
console.log(m2()) // [0, 0]
console.log(m3()) // [0,4] 默认参数有x的默认值,没有y的默认值,那么从默认解构参数获取
// x 和 y 都有值的情况;即有参数,那么只有默认解构参数生效
console.log('x y 都有值的情况', m1({x: 3, y: 8})) // [3, 8]
console.log(m2({x: 3, y: 8})) // [3, 8]
console.log(m3({x: 3, y: 8})) // [3, 8]
// x 有值,y 无值的情况;即有参数,那么只有默认解构参数生效
console.log('x有值,y无值的情况', m1({x: 3})) // [3, 0] 默认解构参数,y有值,则补充
console.log(m2({x: 3})) // [3, undefined] 默认解构参数,y没有值,则为undefined
console.log(m3({x: 3})) // [3, 4]
// x 和 y 都无值的情况;即有参数,那么只有默认解构参数生效
console.log('x和y都无值,即参数是空对象情况', m1({})) // [0, 0];
console.log(m2({})) // [undefined, undefined] 默认解构参数无值,则都为undefined
console.log(m3({})) // [2, 4]
// 传入未定义的z属性的情况;即有参数,只是为空对象,那么只有默认解构参数生效
console.log('传入未定义的z属性的情况', m1({z: 3})) // [0, 0]
console.log(m2({z: 3})) // [undefined, undefined]
console.log(m3({z: 3})) // [2, 4]
2.参数默认值位置
虽然我觉得写代码的时候这个东西没有意义,但是用于学习还是可以了解以下的。你写的时候直接把默认参数放后面就行了。
有默认值的参数如果不是尾参数,该参数传参时不能被省略跳过,且除非显式输入undefined否则无法使用默认参数。
// 例一,默认参数的位置在最前面
function f(x = 1, y) {return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 报错,注意不能省略默认参数
f(undefined, 1) // [1, 1] 只能使用undefined来出发默认参数值
// 例二,默认参数在中间
function f(x, y = 5, z) {return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, 3)
f(1, ,2) // 报错,注意不能省略默认参数
f(1, undefined, 2) // [1, 5, 2] 只能使用undefined来出发默认参数值
// 例三,默认参数在头尾
function f(x = 1, y, z = 3){return [x, y, z];
}
f() // [1, undefined, 3]
f(2) // [2, undefined, 3]
f(3, 2) // [3, 2, 3]
f(4, ,5) // 报错,注意不能省略默认参数
f(undefined, 2, undefined) // [1, 2, 3]
// 例四,null赋值
function foo(x = 5, y = 6) {console.log(x, y);
}
foo(undefined, null) // 5 null 默认参数只能undefined触发,null实际上是赋值即传参,并不会触发默认值
length 是函数对象的一个属性值,指该函数有多少个必须要传入的参数,即形参的个数。形参的数量不包括剩余参数个数,仅包括第一个具有默认值之前的参数个数。与之对比的是, arguments.length 是函数被调用时实际传参的个数。因为length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,后文的 rest 参数也不会计入length属性。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
(function(...args) {}).length // 0
(function (a = 0, b, c) {}).length // 0 第一个具有默认参数之前的参数个数是0
(function (a, b = 1, c) {}).length // 1 第一个具有默认参数之前的参数个数是1
4.作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域,这种语法行为,在不设置参数默认值时,是不会出现的。
函数的参数声明了x,所以函数内的x和全局的x无关
let x = 1;
function f(x, y = x) {console.log(y);
}
f() // undefined,x没有默认值
f(2) // 2y的默认值是x,这时函数内x是没有定义的,从作用域往上找,找到全局变量x。y的默认值和函数内部声明的x无关。
let x = 1;
function f(y = x) {let x = 2;console.log(y);
}
f() // 1所以显然如果全局变量没有x,会报错
function f(y = x) {let x = 2;console.log(y);
}
f() // ReferenceError: x is not defined上面给的链接那个人还写了一种参数x=x,我觉得这没意义,没有人真的这么写,想也知道会出问题。回调函数,当参数是函数时
// example 1:
let foo = 'outer';
function bar(func = () => foo) {let foo = 'inner';console.log(func());
}
bar(); // outer
/*
函数bar的参数func的默认值是一个匿名函数,返回值为变量foo。函数参数的作用域里面,并没有定义变量foo,往上找,所以foo指向外层的全局变量foo,因此输出outer。*/// example 2:
function bar(func = () => foo) {let foo = 'inner';console.log(func());
}
bar() // ReferenceError: foo is not defined
/* 匿名函数里面的foo指向函数外层,但是函数外层并没有声明变量foo,所以就报错了。*/// example 3:
var x = 1;
function foo(x, y = function() { x = 2; }) {var x = 3;y();console.log(x);
}
foo() // 3
x // 1
/*
函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量y,y的默认值是一个匿名函数。
这个匿名函数内部的变量x没有声明,往上找,在函数参数作用域这找到x声明,就指向第一个参数x。
函数foo内部又声明了一个内部变量x,该变量与第一个函数参数x由于不是同一个作用域,函数参数x的作用域是内部变量x的上一级,所以不是同一个变量,就好像你修改全局变量,函数内部变量不会受到影响。
因此执行y后,内部变量x和外部全局变量x的值都没变,变的是函数参数x。*/// example 4:
var x = 1;
function foo(x, y = function() { x = 2; }) {x = 3;y();console.log(x);
}
foo() // 2
x // 1
/*
如果将var x = 3的var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,
所以最后输出的就是2,而外层的全局变量x依然不受影响。*/
返回该函数的函数名
var f = function () {};
f.name //"f"
(function(){}).name //""
const foo = function fun() {};
foo.name //"fun"
构造函数
原型链
1、添加
function Person(age) {this.age = age
}
var person1 = new Person()
var person3 =new Person()
person3.name='bob'
Person.prototype.name = 'kavin'
var person2 = new Person()
console.log(person1.name) //kavin
console.log(person2.name) //kavin
console.log(person3.name) //bob
调用实例对象属性时,先在实例对象查找,没有就顺着原型链查找。
person1、2实际都只有age属性,为什么只有age看下面的new过程,他的name属性是在原型链上找到的
而person3自己有就不用走原型链2、修改
function Person() {function a(){console.log(2)}
}
var person1 = new Person()
var person3 =new Person()
person3.a=function(){console.log(4)}
Person.prototype.a=()=>console.log(3)
var person2 = new Person()
person1.a() //3
person2.a() //3
person3.a() //4
function Person() {this.age=18
}
var person1 = new Person()
var person3 = new Person()
Person.prototype.age=19
var person2 = new Person()
person3.__proto__.age=17
console.log(person1.age) //18
console.log(person2.age) //18
console.log(person3.age) //18
因为自己有function Person() {this.age=18
}
var person1 = new Person()
var person3 = new Person()
Person.prototype.name='Tom'
var person2 = new Person()
person3.__proto__.name='Bob'
console.log(person1.name) //Bob
console.log(person2.name) //Bob
console.log(person3.name) //Bob
因为都没有,就要找链,person3.__proto__和Person.prototype是一个东西
new过程
1.创建空对象,空对象_proto_指向函数原型变量prototype
2.call / apply方法把this指向空对象,传参
3构造函数没返回值,返回对象的属性方法,如果函数return的是非对象(数字、字符串、布尔类型等)会忽略返回值,返回this即空对象; 如果return的是对象,则返回该对象
简易实现
let myNew = function (fn) {if (typeof ctor !== 'function') {throw 'newOperator function the first param must be a function';}//let o={};//o.__proto__ = fn.prototype//o.__proto__.constructor = fnlet o = Object.create(fn.prototype);//从fn的原型对象继承let arg = Array.prototype.slice.call(arguments, 1);//获取参数//let arg=[].slice.call(arguments, 0);let ret = fn.apply(o, arg);//绑定thislet isObject = typeof ret === "object" && ret !== null;//返回类型不是空对象let isFunction = typeof ret === "function";//返回类型是函数return isObject || isFunction ? ret : o;//是对象返回对象,否则返回新建的对象
}
var Person = function() {this.name = "程序员";
};
var p = new Person();
执行顺序
先执行new关键字,创建空对象,空对象继承函数原型变量Person.prototype,再通过call/apply把函数参数传给空对象,函数this获取空对象,this给空对象添加name属性,函数结束,返回this,把this交给变量p// 返回一个对象的 return
var ctr = function() {this.name = "赵晓虎";this.age=18;return {name:"牛亮亮"};
};
// 创建对象
var p = new ctr();
// 访问name属性
console.log(p.name); //"牛亮亮"
console.log(p.age); //undefined
因为返回了对象,所以p={name:"牛亮亮"},这个对象没有age属性//返回非对象数据的构造器,实际返回this
var ctr = function() {this.name = "赵晓虎";this.age=18;return "牛亮亮"
};
// 创建对象
var p = new ctr();
// 访问name属性
console.log(p.name); //"赵晓虎"
console.log(p.age); //18
嵌套函数
嵌套函数如不作为返回值返回,在外部是接触不到的
function a(){function b(){console.log('b')}console.log('a')
}
a.b() //报错a.b不是函数
a()() //报错a(...)不是函数
b() //报错b未定义
构造函数的嵌套函数调用
function a(){this.foo=function(){console.log('foo'+this)}console.log('a'+this)
}
a // 直接返回函数
/*ƒ a(){this.foo=function(){console.log('foo'+this)}console.log('a'+this)
}*/a() //a[object Window]
返回值undefineda.foo() //报错 a.foo is not a functiona().foo() //a[object Window] 然后报错 Cannot read property 'foo' of undefined
从a()可以看到他执行完没有返回东西,所以从undefined的原型对象找不到foonew a() //a[object Object]
返回了对象 a {foo: ƒ}new a().foo() //a[object Object] foo[object Object]
返回值undefined
这个为什么成功找到foo了呢,我的理解是new创建了空对象所以new a()能返回对象a{foo:f},然后对象a的foo属性调用new new a().foo() //a[object Object] foo[object Object]
返回了对象 foo {}
这就可以印证我上面的猜想,,new创建了空对象,new a()返回对象a{foo:f},然后对象a的foo函数和空对象绑定返回如果哪里想错了希望大佬教一下
闭包
没有准确权威的定义,大概为三个条件:访问所在作用域、函数嵌套、在所在作用域外被调用
function a(){var i=0;function b(){i++console.log(i)}return b;
}
var c=a();
c() //1
c() //2
c() //3
var d=a()
d() //1
d() //2
d() //3function a(){var i=0;function b(){i++console.log(i)}return b;
}
a()() //1
a()() //1
var fn = null;
function foo() {var a = 2;function innnerFoo() {console.log(a);}fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
}
function bar() {fn(); // 此处的保留的innerFoo的引用
}
foo();
bar(); // 2虽然例子中的闭包被保存在了全局变量中,但是闭包的作用域链并不会发生任何改变
var fn = null;
function foo() {var a = 2;function innnerFoo() {console.log(c); // 在这里,试图访问函数bar中的c变量,会抛出错误console.log(a);}fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
}function bar() {var c = 100;fn(); // 此处的保留的innerFoo的引用
}
foo();
bar();
回调函数
箭头函数
(1)如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
(2)如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。如果只有一句可以不用大括号和return语句
(3)由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
(4)箭头函数可以与变量解构结合使用
(5)箭头函数内部的this是词法作用域,由上下文确定。(词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的),由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略
const isEven = n => n % 2 === 0;
const square = n => n * n;var f = () => 5;
// 等同于
var f = function () { return 5 };var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {return num1 + num2;
};let getTempItem = id => { id: id, name: "Temp" }; // 报错
let getTempItem = id => ({ id: id, name: "Temp" }); // 不报错const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {return person.first + ' ' + person.last;
}var Person = {firstName:'hello',
lastName:'world',
getFullName:function(){console.log(this)
var first=this.firstName
var fun=()=>{console.log(this)
return this.firstName+this.lastName
}
return fun()
}
}
Person.getFullName()
//{firstName: "hello", lastName: "world", getFullName: ƒ}
//{firstName: "hello", lastName: "world", getFullName: ƒ}
综合案例
在这里插入代码片
this
window:
(嵌套)函数独立调用、IIFE立即执行函数、闭包、隐式丢失
function foo(){console.log(this==window);
}
foo() //truevar a = 0
var obj = {a: 2,foo: function () {function test() {console.log(this.a)}test()console.log(this.a)}
}
obj.foo() //0 2
var a = 0
var obj = {a: 2,foo: function () {(function test() {console.log(this.a)})()console.log(this.a)}
}
obj.foo() //0 2
由于闭包this指向window,而常常需要访问嵌套函数this,所以常用var that=this,闭包中通过作用域查找找到嵌套函数this
var a = 0
var obj = {a: 2,foo: function () {var that = thisfunction test() {console.log(this.a)}return test}
}
obj.foo()() //0var a = 0
var obj = {a: 2,foo: function () {var that = thisfunction test() {console.log(that.a)}return test}
}
obj.foo()() //2
var a = 0;
function foo(){console.log(this.a);
};
var obj = {a : 2,foo:foo
}
//把obj.foo赋予别名bar,造成了隐式丢失,因为只是把foo()函数赋给了bar,而bar与obj对象则毫无关系
var bar = obj.foo;
bar(); //0var a = 0;
function foo(){console.log(this.a);
};
function bar(fn){fn();
}
var obj = {a : 2,foo:foo
}
//把obj.foo当作参数传递给bar函数时,有隐式的函数赋值fn=obj.foo。
//与上例类似,只是把foo函数赋给了fn,而fn与obj对象则毫无关系
bar(obj.foo); //0var a = 0;
function foo(){console.log(this.a);
};
var obj = {a : 2,foo:foo
}
setTimeout(obj.foo,100);//0
var a = 0;
function foo() {console.log(this.a);
};
var obj = {a: 2,foo: foo
}
var p = {a: 3
}
obj.foo() //2
p.foo = obj.foo
p.foo() //3
事件循环
它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
任务队列
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
- 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
一个 Event Loop 中,可以有一个或者多个任务队列
(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。 setTimeout/Promise 等 API 便是任务源,而进入任务队列的是他们指定的具体执行任务。
异步
Promise
- pending: 等待中,或者进行中,表示还没有得到结果
- resolved(Fulfilled): 已经完成,表示得到了我们想要的结果,可以继续往下执行
- rejected: 也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行
var p = new Promise(function(resolve, reject){resolve(1);
});
p.then(function(value){ //第一个thenconsole.log(value);return value*2;
}).then(function(value){ //第二个thenconsole.log(value);
}).then(function(value){ //第三个thenconsole.log(value);return Promise.resolve('resolve');
}).then(function(value){ //第四个thenconsole.log(value);return Promise.reject('reject');
}).then(function(value){ //第五个thenconsole.log('resolve: '+ value);
}, function(err){console.log('reject: ' + err);
})控制台输出:
1
2
undefined
"resolve"
"reject: reject"
var p1 = new Promise( function(resolve,reject){foo.bar();resolve( 1 );
});p1.then(function(value){console.log('p1 then value: ' + value);},function(err){console.log('p1 then err: ' + err);}
).then(function(value){console.log('p1 then then value: '+value);},function(err){console.log('p1 then then err: ' + err);}
);var p2 = new Promise(function(resolve,reject){resolve( 2 );
});p2.then(function(value){console.log('p2 then value: ' + value);foo.bar();}, function(err){console.log('p2 then err: ' + err);}
).then(function(value){console.log('p2 then then value: ' + value);},function(err){console.log('p2 then then err: ' + err);return 1;}
).then(function(value){console.log('p2 then then then value: ' + value);},function(err){console.log('p2 then then then err: ' + err);}
);控制台输出:
"p1 then err: ReferenceError: foo is not defined"
"p2 then value: 2"
"p1 then then err: undefined"
"p2 then then err: ReferenceError: foo is not defined"
"p2 then then then value: 1"
var p1 = Promise.resolve( 1 );
var p2 = Promise.resolve( p1 );
var p3 = new Promise(function(resolve, reject){resolve(1);
});
var p4 = new Promise(function(resolve, reject){resolve(p1);
});console.log(p1 === p2);
console.log(p1 === p3);
console.log(p1 === p4);
console.log(p3 === p4);p4.then(function(value){console.log('p4=' + value);
});p2.then(function(value){console.log('p2=' + value);
})p1.then(function(value){console.log('p1=' + value);
})输出台输出:
true
false
false
false
p2=1
p1=1
p4=1
var p1 = new Promise(function(resolve, reject){resolve(Promise.resolve('resolve'));
});var p2 = new Promise(function(resolve, reject){resolve(Promise.reject('reject'));
});var p3 = new Promise(function(resolve, reject){reject(Promise.resolve('resolve'));
});p1.then(function fulfilled(value){console.log('fulfilled: ' + value);}, function rejected(err){console.log('rejected: ' + err);}
);p2.then(function fulfilled(value){console.log('fulfilled: ' + value);}, function rejected(err){console.log('rejected: ' + err);}
);p3.then(function fulfilled(value){console.log('fulfilled: ' + value);}, function rejected(err){console.log('rejected: ' + err);}
);控制台输出:
"rejected: [object Promise]"
"fulfilled: resolve"
"rejected: reject"
SetTimeout
函数体内容立即执行,第一个参数延迟执行
例子
setTimeout(function() {console.log('timeout1');
})
new Promise(function(resolve) {console.log('promise1');for(var i = 0; i < 1000; i++) {i == 99 && resolve();}console.log('promise2');
}).then(function() {console.log('then1');
})
console.log('global1');
结果:
promise1
promise2
global1
then1
timeout1
ajax
AJAX 是异步的 JavaScript 和 XML(Asynchronous JavaScript And XML)。简单点说,就是使用 XMLHttpRequest 对象与服务器通信。 它可以使用 JSON,XML,HTML 和 text 文本等格式发送和接收数据。AJAX 最吸引人的就是它的“异步”特性,也就是说他可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面
前端学习日志-4-js相关推荐
- 前端学习笔记(js基础知识)
前端学习笔记(js基础知识) JavaScript 输出 JavaScript 数据类型 常见的HTML事件 DOM 冒泡与捕获 流程控制语句 for..in 计时器 let,var,const的区别 ...
- WEB前端学习日志Day4
WEB前端学习日志Day4 今日总结:通过一天的学习了解了样式表的权重,css的层叠性,css的选择符,划分网页上下布局,主要对css的选择符进行深入理解和代码实现. 样式表的权重 样式表的权重关系: ...
- 前端学习日志(Vue)
文章目录 模板语法 插值语法 指令语法 数据绑定 MVVM模型 数据代理 事件处理 计算属性 监视属性 进行监听 深度监视 条件渲染 列表渲染 1.基本列表 列表过滤 列表排序 生命周期 Vue组件的 ...
- JS学习日志15 -- JS基础--忍者代码
前言 从头开始对javascript进行学习,每天定个小目标,学习一点,期待学习完后,对js的认知会发生什么变化~~ :JS基础知识 一.忍者代码 过去的程序员忍者使用这些技巧,来使代码维护者的头脑更 ...
- 前端学习:Vue.js基本使用
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. Vue教程文档: https://cn.vuejs.org/v2/guide/ 定义 实例: ne ...
- 前端学习之touch.js与swiper学习
Touch.js是移动设备上的手势识别与事件库,改框架基于原生js,操作简单,主要分drag,swipe,rotate,scale,tab,hold,touch操作. swiper是一个移动端触摸滑动 ...
- 【前端学习】Day-16 JS基础、循环、函数、数组、字符串、字典
文章目录 1. 了解JavaScript 2. js小案例 3. JavaScript基础 4. js循环 5. js函数 6. js数组 7. js字符串 8. js练习题 1. 了解JavaScr ...
- 前端学习笔记——node.js
初识 Node.js 什么是 Node.js Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境. Node.js 中的 JavaScript 运行环境 注意: 浏 ...
- 【前端学习日志】HTML表格表单注册页面案例+CSS选择器
今日学习 一.HTML部分 1.表格的基本语法 2.表头单元格标签 3.还是表头单元格标签 4.小说排行榜案例 5.合并单元格 6.无序列表 7.有序列表 8.自定义列表 9.表单域 10.Input ...
最新文章
- Solr 3.5:配置mmseg4j同义词(已经配置好中文分词)
- SharePoint Server 2007 Web内容管理中的几个关键概念
- Unix toolbox注解2之Linux系统状态用户和限制
- python教程:apscheduler模块使用教程
- 阿里员工都在用的知识管理工具,究竟有何特别?
- 微软工程师测试题——未来
- 如何在linux环境下安装kvm,如何在Linux发行版上安装和配置KVM和Open vSwitch?
- vba excel 退出编辑状态_偷梁换柱之EXCEL编辑保护和VBA隐藏代码保护的解锁
- 深入理解CSS六种颜色模式
- dbForge mysql数据库比对
- 令人激动!谷歌推强化学习新框架「多巴胺」,基于TensorFlow,已开源丨附github...
- 2gt;MSVCRTD.lib(MSVCR100D.dll) : error LNK2005: _calloc 已经在 LIBCMTD.lib(dbgcalloc.obj) 中定义...
- esp8266电池供电方案_(普通照明、应急照明、事故照明)方案解读
- 步步为赢,做好数据分析的7个步骤
- Kafka Exception:Bootstrap broker disconnected Consumer disconnected
- ChatGPT所有插件详细教程
- 华为 ensp 部分查询方法
- Linux下minikube启动失败(It seems like the kubelet isn't running or healthy)
- android手机慢,揭秘Android手机变慢的三大原因与对策
- 美拍高颜值短视频一键解析批量保存到电脑中