函数声明的三种方式
- 函数关键字(function)
// 函数调用可以写在函数声明前面
function func(params) {
console.log('fucntion的声明方式')
}
- 函数表达式或称函数字面量(Function Literals)
// 函数表达式必须等到解释器执行到那一行才进行解释,函数调用要写在函数表达式后面
// console.log(funcExp.length) //2
const funcExp = function(params,params2) {
console.log('函数表达式声明方式')
}
// Lambda表达式(箭头函数)
const funcExp1 = params => {
console.log('函数表达式之箭头函数声明方式')
}
- Function()构造函数
const funcExp2 = new Function('x','alert(1);') //alert(1)
函数调用的四种方式
- 声明后直接调用
// 声明一个函数并调用
function func() {
alert("Hello World");
}
func();
// 使用函数的Lambda表达式定义函数,然后调用
var func1 = () => {
alert("你好,程序员");
};
func1();
- 方法调用模式
const objFunc = {
name: '函数',
myFunc: () => {
console.log('将函数赋值给对象的属性后不再称为函数称为方法')
}
}
- 构造器调用模式
// 定义一个构造函数
var Person = function() {
this.name = "程序员";
this.sayHello = function() {
alert("你好,这里是" + this.name);
};
};
// 调用构造器,创建对象
var p = new Person();
// 使用对象
p.sayHello();
- apply、call调用模式
var func2 = function() {
this.name = "程序员";
};
var o = {};
func2.apply(o);
alert(o.name);
操作this(call apply bind)
const name = "小明",age = 17;
const obj = {
name: "小张",
age: 19,
myFunc: function(gender, country){
console.log(this.name + "年龄:" + this.age + ",性别:" + gender + ",所在城市:" + country);
},
}
obj.myFunc();// 小张年龄:19,性别:undefined,所在城市:undefined
const newObj = {
name: "小红",
age: 67
}
obj.myFunc.call(newObj, "男", "北京"); // 小红年龄:67,性别:男,所在城市:北京
obj.myFunc.apply(newObj, ["女", "广州"]); // 小红年龄:67,性别:女,所在城市:广州 (apply传一个数组)
obj.myFunc.bind(newObj, "男", "深圳")(); // 小红年龄:67,性别:男,所在城市:深圳 (bind 返回的是一个新的函数,你必须调用它才会被执行。)
obj.myFunc.bind(newObj, ["女", "上海"])(); // 小红年龄:67,性别:女,上海,所在城市:undefined (bind和apply传参方式一样)
const let 什么时候用
- 只对那些在逻辑、语义和设计意图上确实不应当被改变的常量,使用 const 声明。
- 换句话说,你认为某个量应该表现为常量,使用const。你认为某个量应该表现为可变量,就用 let。
闭包(工厂函数)
示例来自MDN
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
栈内存、堆内存
js数据类型
// 8种
Number、String、Boolean、Null、undefined、object、symbol、bigInt
bigInt使用
// 数字后面加n
console.log(90071992547409956n);// 12 90071992547409956n
// 构造函数形式
const bigIntNum = BigInt("9007199254740995");
console.log(bigIntNum)// 9007199254740995n
js数据类型分类
- 基本类型(单类型):String、Number、Boolean、null、undefined
- 引用类型:object。包含function、array、date
NaN是Number类型中的一个特殊值
示例引导
问题:const定义的值能改吗?
答:const定义的基本类型不能改变,但是定义的对象是可以通过修改对象属性等方法来改变的,例:
const a = 1;
a = 2;
console.log(a);// Uncaught TypeError: Assignment to constant variable.
const b = {};
b.name = 1;
console.log(b);// {name:1}
存储类型
- 栈内存:主要用于存储基本类型的变量,包括Boolean、Number、String、Undefined、Null、对象变量的指针
- 堆内存:主要存储像对象这种变量类型
存储大小
- 栈内存中的变量一般都是已知大小或者有范围上限的,算作一种简单存储。
- 堆内存存储的对象类型数据对于大小这方面,一般都是未知的。
这也是为什么null作为一个object类型的变量却存储在栈内存中的原因。
const定义的值能改吗?的具体解释
- 当定义const对象时,我们说的常量其实是指针,就是const对象对应的堆内存指向是不变的,但是堆内存中的数据本身的大小或者属性是可变的。
- 当定义基础变量时,const定义的值就相当于const对象的指针,是不可变的。
new关键字
new根据构造函数生成新实例,这个时候生成的是对象,而不是基本类型。
var a = new String('123')
var b = String('123')
var c = '123'
console.log(a==b, a===b, b==c, b===c, a==c, a===c) // true false true true true false
console.log(typeof a) // object
var a = new String('123')
var b = new String('123')
console.log(a==b, a===b)// false false
深拷贝
深拷贝是拷贝储存在内存堆中的对象,而浅拷贝是从内存栈中拷贝
// 对象的深拷贝
var clone = function (obj) {
if(obj === null) return null
if(typeof obj !== 'object') return obj;
if(obj.constructor===Date) return new Date(obj);
var newObj = new obj.constructor (); //保持继承链
for (var key in obj) {
if (obj.hasOwnProperty(key)) { //不遍历其原型链上的属性
var val = obj[key];
newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除与函数名的耦合
}
}
return newObj;
};