js重难点

文章目录

var,const,let

  • 对于var声明的变量,变量声明会提升,即可以理解有个预处理,把整个js代码的var声明的变量首先处理,所以var声明的变量可以重复声明
  • let声明的变量具有块级作用域,不会发生变量声明提升,所以不可以重复声明,但是如果声明的变量在另一个块级作用域中是可以的,因为上一个变量在块级作用域中运行之后就被清除了
  • 通过eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在eval()执行的时候才会被创建。
console.log(x);
var x=5;\\undefined;因为只有变量的声明提升,var x放到了代码最前头,但是赋值语句位置不变

for(let x in [1,2]){}//程序运行完之后x被清除
let x;//不会报错

{let x=5;}
console.log(x);//error,只要{}就可以当做块级作用域,
  • const具有与let一样的块级作用域,const声明的原始值不能修改,引用类型的属性值可以修改
let arr = [1,2,3,4,5];
for(const value of arr){
	console.log(value);
}//1,2,3,4,5
//以上代码会成功执行,for in 和for of它们两个都是一种严格的迭代语句,对于对象中的每一个非符号键属性值,有一个指定的语句块被执行。也就是每一次循环,都会产生一个块级作用域来完成每个变量的行为。
//for-in语句是一种严格的迭代语句,用于枚举对象中的非符号键属性,for-of语句是一种严格的迭代语句,用于遍历可迭代对象的元素

for(const i = 0;i<3;i++){
	console.log(i);
}//错误

引用类型

  • 引用类型的变量是存储在内存上的,这意味着实际上我们操作的这个变量实际上是个指针,储存的是这个内容在堆内存的地址。包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身。
  • 引用值是保存在内存中的对象。JavaScript不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象时,实际上操作的是对该对象的引用,而非实际对象本身,
  • 当复制值时,复制的其实也是这个对象的引用,可以理解为复制了这个对象在堆内存的地址
  • 传递函数参数时,传递的引用值也是传递的这个对象的引用,所以函数内对象属性的修改会影响到函数外
function setName(obj) {
	obj.name = "Nicholas";
    obj=new Object();//这相当于Obj存了另一个对象在内存的地址,此后Obj的改变与person对象没有关系了,所以后面的改变也不会影响他
    obj.name="gulala";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"

执行环境(execution context)

function buildUrl() {
	let qs = "?debug=true";
    with(location){
		let url = ks + qs;//注意这边按作用域链,ks在函数里没有定义,则作用域链向外找到location.ks,
        console.log(url);
	}
    //return url;//错误,url在with的块级作用域,此时并没有声明url
}
buildUrl()

基本引用类型

string

number

isInteger()方法,用于辨别一个数值是否保存为整数
console.log(Number.isInteger(1.00)); // true

集合引用类型

Array

//Array.from(类数组对象,[映射函数,this的值])
const a = [1, 2, 3, 4];
console .log(Array.from(a,x=>x**2));//[1,4,9,16]
console.log( Array.from(a, function(x) {return x**this.exponent}, {exponent: 2}));//[1,4,9,16]

//Array.of():将一组参数转换为数组实例
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]

//keys()返回数组索引的迭代器,values()返回数组元素的迭代器,而entries()返回索引/值对的迭代器
const a = ["foo", "bar", "baz", "qux"];
for (const [idx, element] of a.entries()) {
    alert(idx);//0 1 2 3
    alert(element);//foo bar baz qux
}

.fill(填充值,开始索引,结束索引‘不包括’)//对数组进行覆盖填充
.cope

let s=[1,2,3];
alert(s);//对每个元素调用toString()方法
s.toString();//toString()对数组的每个值都会调用其toString()方法得到等效字符串,返回这些等效字符串拼接而成的一个逗号分隔的字符串
s.toLocalString();//与toString不同的是调用的是每个元素的toLocalString()方法
s.valueOf();//返回数组本身

数组尾部:push()添加,pop()删除
数组开头:unshift()添加,shift()删除

排序方法:
reverse()//反序
console.log(d.sort((a,b)=>a-b))//升序排法,如果sort不传比较函数则会把数组的元素转化为字符串再比较

迭代器

  • 生成迭代器:iter=arr[Symbol.iterator](),使用iter.next()可以获取到下一个值
  • 自定义的对象不能使用迭代,可以自己添加[Symbol.iterator]()方法
class Counter {
	constructor(limit) {
	this.limit = limit;
    }
    [Symbol.iterator]() {
        let count = 1,
        limit = this.limit;
        return {
            next() {
                if (count <= limit) {
                return { done: false, value: count++ };
                } else {
                return { done: true };
        		}
    		},
        	return() {
                console.log('Exiting early');
                return { done: true };
        	}
        };
    }
}

生成器

  • 生成器函数只能用.next()方法执行
  • 执行到yield "foo"处就会暂停,然后计算出要产生的值是"foo",必须调用next()方法才能继续执行
  • 执行到return处就会返回done:true
  • yield关键字必须直接位于生成器函数定义中,出现在嵌套的非生成器函数中会抛出语法错误;嵌套在语句中是可以的
  • 上一次让生成器函数暂停的yield关键字会接收到传给next()方法的第一个值
  • 使用星号增强yield的行为,让它能够迭代一个可迭代对象,从而一次产出一个值 :for (const x of [1,2,3]){yield x;}等价于yield* [1,2,3];yield*的值是关联迭代器返回done: true时的value属性 。对于普通迭代器来说,这个值是undefined。对于生成器函数产生的迭代器来说,这个值就是生成器函数返回的值
function* generatorFn() {
    yield 'foo';yield 'bar';
    return 'baz';
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // { done: false, value: 'foo' }
console.log(generatorObject.next()); // { done: false, value: 'bar' }
console.log(generatorObject.next()); // { done: true, value: 'baz' }

1.生成器对象作为可迭代对象
function* nTimes(n) {
    while(n--)yield;}//迭代n次

2.使用yield实现输入和输出
function* generatorFn(initial) {
    console.log(initial);
    console.log(yield);
    console.log(yield);}
let generatorObject = generatorFn('foo');
generatorObject.next('bar'); // foo
//开始执行生成器函数,暂停在第一个yield处,但不会取到值
generatorObject.next('baz'); // baz
//第一个yield在上一次被暂停,这次调用next()方法获得baz的值,然后执行第一个console.log(yield);语句,然后暂停在第二个yield处
generatorObject.next('qux'); // qux
//同理

function* generatorFn() {
	return yield 'foo';}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // { done: false, value: 'foo' }
console.log(generatorObject.next('bar')); // { done: true, value: 'bar' }

3.产生可迭代对象
function* generatorFn() {
	console.log('iter value:', yield* [1, 2, 3]);
}
for (const x of generatorFn()) {
	console.log('value:', x);
}
// value: 1
// value: 2
// value: 3
// iter value: undefined
//要理解,对于console.log()语句表达式()里面的值必须要算完(不止算完,要执行完,)才会执行,yield*完全可以看为自动执行.next()方法,计算出值然后给x,会一直计算到return语句出现或者done:true情况下,当计算到3的时候此时done:false,意味着还可以继续执行,但是执行之后为undefined,但是undefined的值不会返回给x,所以iter value语句输出

function* innerGeneratorFn() {
    yield 'foo';
    return 'bar';
}
function* outerGeneratorFn(genObj) {
	console.log('iter value:', yield* innerGeneratorFn());
}
for (const x of outerGeneratorFn()) {
	console.log('value:', x);
}
// value: foo
// iter value: bar
//done:true的值都不会被for循环取到

4.使用yield*实现递归算法
function* nTimes(n) {
    if (n > 0) {
        yield* nTimes(n - 1);
        yield n - 1;}}
for (const x of nTimes(3)) {
	console.log(x);}
//首先暂停到nTimes(2),计算nTimes(2),又暂停在nTimes(1),依次最后计算nTimes(0),无结果,
// 0
// 1
// 2

对象

对象解构

  • 对象解构语法,可以在一条语句中使用嵌套数据实现一个或多个赋值操作。简单地
    说,对象解构就是使用与对象匹配的结构来实现对象属性赋值。
  • 解构并不要求变量必须在解构表达式中声明。不过,如果是给事先声明的变量赋值,则赋值表达式必须包含在一对括号中
  • 解构的属性值如果是个对象,复制的时候是浅复制,即只复制引用,修改原对象的属性值也会影响到赋给的变量
let person={
    name:"matt"
};
let {name:personName,job="km"}=person;
console.log(personName,job);//matt km

let {length}="matt";//=>let {length:length}="matt";
//首先创建了一个"matt"的对象,然后获取到了length属性,然后销毁了这个对象
//null,undefined不能被解构
console.log(length);//4

原型

  • 只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原
    型对象)。默认情况下,所有原型对象自动获得一个名为constructor的属性,指回与之关联的构造函数。即Person.prototype.constructor指向Person
  • 每次调用构造函数创建一个新实例,这个实例的内部[[Prototype]]指针就会被赋值为
    构造函数的原型对象。
  • 每个对象上暴露__proto__属性,通过这个属性可以访问对象的原型 。Person.prototype.__proto__===Object.prototype
  • 原型中包含的引用值会在所有实例间共享
  • 重写整个原型会切断最初原型与构造函数的联系,但在重写之前实例化的对象引用的仍然是最初的原型
function Person(){}
let p=new Person();
/*这样不算重写,只是修改原型
Person.prototype.sayName=function(){
    console.log(5);}
*/
Person.prototype = {
    constructor: Person,//这里由于创建了函数,导致函数也有prototype属性,所以手动设置constructer指向Person
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName() {
    	console.log(this.name);
	}
};
p.sayName();//error,重写之后p还是指向最初的原型

原型链

  • 任何函数的默认原型都是一个Object的实例,这意味着这个实例有一个内部指针指向
    Object.prototype
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // true

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fS9FAINu-1612060700409)(…/…/image/image-20210129160210553.png)]

  • 派生类的方法可以通过super关键字引用它们的原型。在类构造函数中使用super()可以调用父类构造函数,不要在调用super()之前引用this,否则会抛出ReferenceError 。在静态方法中可以通过super.say()调用继承的类上定义的静态方法
  • 如果在派生类中显式定义了构造函数,则要么必须在其中调用super(),要么必须在其中返回一个对象
  • 抽象基类:不实例化只提供给其他类继承。new.target保存通过new关键字调用的类或函数。通过在实例化时检测new.target是不是抽象基类,可以阻止对抽象基类的实例化。
class Person{
    constructor(name){
        this.name=name;
        this.sayName=()=>console.log(this.name);
    }
    sayAge(){
        console.log(15);
    }
    static say(){
        console.log("Person");
    }
}
class Child extends Person{}//继承类


代理

  • 创建代理:proxy=new Proxy(目标对象,处理程序对象)
  • 撤销函数和代理对象是在实例化时同时生成的。
const target = {
    foo: 'bar',
    baz: 'qux'
};
const handler = {
    get(trapTarget, property, receiver) {
        let decoration = '';
        if (property === 'foo') {
        	decoration = '!!!';
		}
		return Reflect.get(...arguments) + decoration;
	}
};
const { proxy, revoke } = Proxy.revocable(target, handler);
revoke();//撤销了代理和目标对象的联系,且不可逆

函数

基本方法

  • 函数中传递参数都是按值的,如果把对象作为参数传递,那传递的便是这个对象的引用,函数中传递多少个参数,这些参数合起来会当成一个arguments的伪数组对象
  • arguments对象的长度是根据传入的参数个数,对于命名参数而言,如果调用函数时没有传这个参数,那么它的值就是undefined。对于传入的参数而言,arguments和命名参数保持同步(修改任意一方的值另一方也会发生修改)
  • 可以显示定义默认参数,这个默认参数也可以是函数:age=age(),但age()只会在调用函数时没有传入对应参数才会执行。
  • 函数声明会在任何代码执行之前先被读取并添加到执行上下文,而函数表达式不会发生函数声明提升,
  • arguments.callee是一个指向arguments对象所在函数的指针
  • 函数对象的属性caller引用的是调用当前函数的函数,如果是在全局作用域中调用的则为null
  • new.target:检测函数是否使用new关键字调用的。如果函数是正常调用的,则new.target的值是undefined;如果是使用new关键字调用的,则new.target将引用被调用的构造函数
  • 函数的length属性保存函数定义的命名参数的个数,prototype属性是保存引用类型所有实例方法的地方

this

  • 参考:https://www.cnblogs.com/pssp/p/5216085.html
  • this指向最终调用他的对象
  • new操作符会改变函数this的指向,指向new的对象
  • 在有返回值情况下,如果返回的是对象则this指向的是返回的对象,如果返回的不是对象或者是nullthis还是指向函数的实例
  • 在箭头函数中,this引用的是定义箭头函数的上下文
  • 在事件回调或定时回调中调用某个函数时,this值指向的并非想要的对象。此时将回调函数写成箭头函数就可以解决问题。这是因为箭头函数中的this会保留定义该函数时的上下文

apply,call,bind

  • apply(),call()会执行函数,bind()方法会创建一个新的函数实例,其this值会被绑定到传给bind()的对象:var f=sayAge.bind(0)
apply(this,参数数组)//say.apply(this,[1,2,3])
call(this,参数)//say.call(this,...[1,2,3])
//apply,call可以修改this值,可以任意对象设置为函数的作用域,切换执行上下文
var age=6;
var o={age:16};
function sayAge(){
    console.log(this.age);
}
sayAge();//6,在全局调用,this指向winbdow
o.sayAge=sayAge;
o.sayAge();//16
sayAge.apply(o);//16 指定o为this值,手动修改了作用域

闭包

  • 作用域链:定义函数时,会为它创建作用域链,预装载全局变量对象,然后创建函数内部的活动对象(函数内声明的变量),并将它推入前端,函数访问变量时就会从作用域链查找变量,简而言之就是函数内部检索变量时会从函数内部开始检索,然后再检索函数外部的,一级一级往上检索。函数执行完毕时,函数内部的活动对象会被销毁
  • 在一个函数内部定义的函数会把其包含函数的活动对象添加到自己的作用域链中,当返回匿名函数之后,匿名函数仍然保持着作用域链,继续对函数内部的变量进行引用,因此函数内部的活动对象不会被销毁,必须销毁匿名函数object.getIdentityFunc()=null才能销毁函数内部的活动对象
  • 每个函数在被调用时都会自动创建两个特殊变量:thisarguments。内部函数永远不可能直接访问外部函数的这两个变量,但其他变量都可以访问,因此执行匿名函数时若无法查找到this,则this会指向window,但是可以在外部函数保存thislet that=this;,那么内部函数可以访问that
window.identity = 'The Window';
let object = {
    identity: 'My Object',
    getIdentityFunc() {
        return function() {
        	return this.identity;
        };
	}
};
console.log(object.getIdentityFunc()()); // 'The Window'
//虽然作为对象的方法调用,但内部函数无法查找到this值,由此this指向window

window.identity = 'The Window';
let object = {
    identity: 'My Object',
    getIdentityFunc() {
        let that = this;//保存了this值,object.getIdentityFunc()作为对象的方法调用,this指向object,因此that指向object,后续内部函数也可以访问that
        return function() {
        	return that.identity;
        };
	}
};
console.log(object.getIdentityFunc()()); // 'My Object

Promise

  • 参考:http://blog.codingplayboy.com/article/124#%E5%89%8D%E8%A8%80
  • http://blog.codingplayboy.com/article/144

未完待续

上一篇:19-Python基础知识学习-----迭代器与生成器


下一篇:迭代器iter()和生成器yield