分类 前端 下的文章

timg.jpg

从技术手段上提高打包速度

跟上技术的迭代(升级node,npm或者yarn),因为webpack是基于node的,node的升级对webpack性能也会有提升,npm中很多的依赖包存在升级,那么对打包的速度也是有提高的
在尽可能少的模块上应用loader,loader的使用会对更多的文件去解析,所以在配置文件中,比如要把node_modules中的文件进行排除,因为可能依赖项已经被打包过,再打包一遍会更浪费事件
关于plugins的使用,尽量使用社区/官方推荐或者非常流行的插件,因为通常这类插件的稳定性和易用性得到了大多数人的认可,而第三方公司或者私人的插件可能会造成对项目优化不友好的地方
所以尽量避免使用plugins

通过resolve配置来提升打包速度

webpack中的resolve可以帮助我们引入第三方模块或者组件的时候,可以能够更方便的引入,比如通常在写vue的时候

import commonHeader from "../components/commonHeader"

不用指定其组件的后缀“vue”就可以找到,那么vue-cli是如何做到的呢,webpack配置就是如下:

resolve: {
    extensions: ["vue", "js", "jsx"]
}

可以通过这样简单配置,如果在目录下,没有找到对应的文件,那么就会再次遍历去寻找配置项指定后缀文件,但是这样的配置如果我们配置的过多的话,比如像这样

["jpg", "png", "json"]

一股脑地全部配上,这样就会在webpack打包中增加压力,延缓打包速度(因为要多次遍历查询)

所以一般的解决方案是:“只配置逻辑文件,如jsx,js,vue”等

如果我们引入配置的时候,引入的路径是这样的

import common from "./common/"

会发现,当组件的名称如果叫index.**时,这句代码是正确的,如果是其他名称,就‘

会报错,是因为webpack的默认项会把index当作主文件,那么我们也可以通过这样的配置进行更改:

mainFiles: ["index", "common"]

resolve中也还有其他常用的配置

比如alias(别名),比如我要这样引入

import common from "shenhao"

我们可以设置一个别名

alias: {
    shenhao: path.resolve(__dirname, ../src/common)
}

然后我们就可以使用alias进行引入,感觉通过这样的描述会感觉这个属性非常鸡肋,其实并不然,比如非常复杂的目录结构,要引入一个包要写很长的路径

如果写过vue的开发者来讲,@符号就表示src下的根目录,那么这个@就是一个别名

但是为了webpack的打包性能,尽量不要使用过多的配置alias和上面的extensions,配置项目中最常用的即可哟

使用DllPlugin提高打包速度

我们尝试使用一个babel打包一个react的程序,如图:

import React, { Component } from 'react';
import ReactDom from 'react-dom';
import _ from 'lodash';


class App extends Component {
    render() {
        return (
            <div>
                <div>{_.join([1,2,3,4,5], "...")}</div>
            </div>
        )
    }
}


let haha = document.createElement("div");
document.body.appendChild(haha);
ReactDom.render(<App />, haha);

经过多次打包,速度平均稳定在

Image.png

那么问题来了,我们每一次打包都会分析react,react-dom等库,我们可以尝试把这些库只分析一次,之后的打包读这些分析过的文件即可

首先我们需要额外的分析这些第三方库

新建webpack.dll.js

const path = require("path");


module.exports = {
    mode: "production",
    entry: {
        vendors: ["react", "react-dom", "lodash"]
    },
    output: {
        path: path.resolve(__dirname, 'dll'),
        filename: '[name].dll.js',
        library: "[name]"
    }
}

我们向外暴漏一下全局变量,然后我们则要让打包后的index.html去引入这个第三方库编译后的js
ps: 要让打包后的index.html自动引入,则需要add-asset-html-webpack-plugin这个插件,做一个简单配置即可(必须)

但是我们在main.js中的引入

根本引入的不是我们编译好的js文件,还是引入的是node_modules中的

  1. 让main.js引入的文件映射到编译过的第三方库文件中

首先配置一下,webpack.dll.js中我们增加一句代码

plugins: [new webpack.DllPlugin({
    name: "[name]",
    path: path.resolve(__dirname, "dll/[name].manifest.json")
})]

引入了webpack之后,使用了webpack中的DllPlugin模块,生成的分析文件的name就是暴漏的全局变量,path就是分析文件的地址

我们将使用全局变量+分析文件来做映射,让代码中的import这样的语句,映射到我们分析的js文件中,而不是引入node_modules中

所以在webpack.config.js中加入

new webpack.DllReferencePlugin({
   manifest: path.resolve(__dirname, "dll/vendors.manifest.json")
})

这边我们传递参数的内容就是生成出来的映射文件,这个时候我们重新打包会发现,打包速度会少200-300毫秒(根据电脑性能不同)

流程梳理:

将不会变更的库进行单独打包,并且暴漏一个全局变量(核心)
打包之后使用DllPlugin进行分析,生成一个映射文件,条件是必须有一个全局变量
在打包main.js中,webpack会对引入的模块进行查询,而common.js使用了DllReferencePlugin这个插件,会在对应的映射文件中查询如果对应的单独库js文件存在引入的模块,那么main.js将直接把单独库js文件中的对应的内容为自己所用,就不会在去node_modules中去寻找模块,通过这一系列,将减少打包时间

那么在大型项目中,需要拆分很多很多的dll.js和manifest.json,那么如何做到“智能的”让webpack使用DllReferencePlugin和add-asset-html-webpack-plugin呢,在看这个问题之前,我们来尝试一下传统方法

不智能的做法:

entry: {
        vendors: ["lodash"],
        react: ["react", "react-dom"]
    },

我们假设要生成2个分析文件

那么我们在webpack.config.js需要这样做

plugins: [new HtmlWebpackPlugin({
        filename: 'index.html'
    }), new AddAssetsHtmlPlugin({
        filepath: path.resolve(__dirname, "dll/vendors.dll.js")
    }),new AddAssetsHtmlPlugin({
        filepath: path.resolve(__dirname, "dll/react.dll.js")
    }), new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, "dll/vendors.manifest.json")
    }),new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, "dll/react.manifest.json")
    })]

居然要配置2遍插件

每个被分析的js都要暴漏一个全局变量,这个之前也说过,需要把每一个文件添加到html中,有了全局变量之后,才能让webpack去用json去查询模块

那么我们假象一下,一个项目如果需要规定上10个,上100个的入口模块,那么就要配置200次(100次添加js到html,100次查询json依赖文件)

这显然是不可取的,所以我们可以用相对智能的方式去做哟,需要用到node一点东西哟;

智能的做法:

思路1: 在运行之前,肯定是解析过了dll.js的,所以运行webpack.config.js之前,项目中应该会有100个.dll.js , 100个.manifest.json
我们抽象一下webpack.config.js中的plugins,定义一个数组

const fs = require("fs");
const plugins = []; // 抽象plugins数组
// 读取dll目录下的数据
let readFileList = fs.readdirSync(path.resolve(__dirname, "dll"));
console.log(readFileList);

// 打印结果:
[ 'react.dll.js',
  'react.manifest.json',
  'vendors.dll.js',
  'vendors.manifest.json' ]

循环这个数组

const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
    if(/.*\.dll.js/.test(file)) {
        plugins.push(new AddAssetHtmlWebpackPlugin({
            filepath: path.resolve(__dirname, '../dll', file)
        }) )
    }
    if(/.*\.manifest.json/.test(file)) {
        new webpack.DllReferencePlugin({
            manifest: path.resolve(__dirname, "dll", file)
        }) )
    }
})

判断循环的当前file名称是不是dll.js,如果是dll.js,就在数组中添加对应的AddAssetHtmlWebpackPlugin 插件,反之就DllReferencePlugin 添加这个插件

大功告成,所以每当有一个新的入口时候,只需要

entry: {
        vendors: ["lodash"],
        react: ["react", "react-dom"]
    },

在webpack.dll.js随意配置即可, 但是每次打包之前,必须要先生成dll.js和manifest.json文件

控制包文件大小

在日常写代码的时候,一些引入的模块,没有用到,那么可以借助treeshaking和codesplit一些代码分割的插件,来控制包大小

多线程打包

webpack基于node的,是单线程进行打包,我们可以在单页应用使用thread-loader来对单页应用进行多线程进行打包,多页应用则使用parallel-webpack进行打包,还有一个是happypack

合理使用soucemap

合理选用适合项目的soucemap模式,过细的soucemap会增大打包压力,所以根据调整soucemap也可以调优

结合stats

一些第三方的网站可以分析webpack的打包文件,分析出打包的文件,哪个文件大,哪个文件慢,从而达到目的性的调优

开发环境内存编译

在开发环境时,使用的webpack-dev-server就是通过内存存储打包后的文件,所以内存的读写一定比硬盘读写快,所以使用内存编译的方式,打包速度也会加快

开发环境无用插件剔除

开发环境下,不需要过多的插件进行压缩等等,所以剔除无用插件也可以达到调优目的;

这段时间一直没有更新文章,实在是有点忙,所以以后基本都会每周周更,文章其实就是自己学习的一个成果展示,并不是什么科普文章哟

今天我们来聊聊reduce,之前呀我们在写业务的时候,循环非常喜欢forEach, for in,map等循环api,但是reduce和这些有什么区别呢,其实reduce循环和刚刚提到这几个循环作用都是一样,但是适用于场景也不一样,它能干其他循环干的了的,也能干其他循环干不了的,还有一个很好的优点就是:很装逼~~, 而且reduce毕竟是高阶函数,在公司中合适的用到它,也能适当提示代码质量。

我喜欢用例子去解决一个知识点,所以我们从例子出发

let arr = [1, 2, 3, 4, 5];
let result1 = arr.reduce((prev, current, index, arr) => {
  console.log(prev, current, index, arr);
  return prev + current;
});
console.log("最后结果:" + result1);
这是一个数组值求和的例子,result1就是结果

微信截图_20191110204719.png

下来描述一下reduce的用法: reduce为数组提供遍历方法,每一次遍历都会执行一次回调函数;

prev: 上一次回调函数返回的值
current: 当前遍历的数组元素
index: 循环当前索引
arr: 调用reduce函数的数组

reduce方法除了回调函数的四个参数,还有一个参数叫做initialValue,字面意思: 初始化值,那么设置这个初始化值有什么用呢?

上面的那个图片例子中的第三个参数分别是1,2,3,4,只循环了4次,执行了4次回调函数,这并不符合常理,但是如果我们把initialValue设置成0,会如何呢?

微信截图_20191110211155.png

设置了初始值,我们就从0开始了,可见如果不设置初始值,reduce会自动跳过第一个直接进行第二次循环;

那么数组为空,我们看会造成什么?

微信截图_20191110212059.png

浏览器会提示我们当数组为空,如果不设置初始值会报错,所以我们在循环的时候,为了代码严谨和安全:

务必要设置initialValue


reduce:简单场景

  1. 数组求和

     let arr = [1,2,3,4];
     let sum = arr.ruduce((pre,cur) => pre*cur);
    

reduce: 高级场景

  1. 统计数组中的元素出现的次数

     let arr = ["昊儿", "王航", "范冰冰", "王航"];
     let result1 = arr.reduce((prev, current, index, arr) => {
       if (current in prev) {
        // 如果存在,就加一
        prev[current]++;
       } else {
        prev[current] = 1;
       }
       return prev;
     }, {});
    
     console.log(result1);
    

结果:

微信截图_20191110214157.png

  1. 数组去重复

     let arr = ["昊儿", "王航", "范冰冰", "王航"];
     let result1 = arr.reduce((prev, current, index, arr) => {
       if (!prev.includes(current)) {
     // 如果不存在
     prev.push(current);
       }
       return prev;
     }, []);
     console.log(result1);
    

微信截图_20191110214645.png

  1. 二维数组转换为一维数组

     let arr = ["昊儿", "王航", "范冰冰", "王航", [1, 2, 3]];
     let result1 = arr.reduce((prev, current, index, arr) => {
       return prev.concat(current);
     }, []);
     console.log(result1);
    

微信截图_20191110215557.png

  1. 多维数组转换为一维数组

     let arr = ["昊儿", "王航", "范冰冰", "王航", [1, 2, 3, [2, 3, 4]]];
    
     let test = function(cur) {
       return cur.reduce((prev, current, index, arr) => {
     return prev.concat(Array.isArray(current) ? test(current) : current);
       }, []);
     };
     console.log(test(arr));
    

看完这些例子,我会说:真香...哈哈哈
timg.jpg

这两天一直在搜集关于JS的原型方面的知识,今天我来总结一下关于原型和原型链等周边相关知识,在这里做一个易于理解的解释。
如果文章有误区或者有错误,请评论欢迎指出;

首先原型是什么?

原型是js继承的基础,js的继承就是原型的继承

Function的原型对象

我们先来看一张图,画的比较草率

微信图片_20190929202953.jpg

首先我们创建了一个函数person,那我们通过打印这个函数,会发现里面有一个prototype这个属性,那么这个prototype指的是这个person函数的原型,那么我们的函数原型中有一个构造函数(construtor)指向的就是person函数

那么我们就知道了函数中的prototype就是原型,原型里面的构造函数指的其实就是我们声明的函数;
搞清楚这个关系之后,我们可以通过new操作符去创建一下构造函数指向新的对象:

var obj = new person();

打印这个obj显示的是_proto_,这个_proto_其实指的就是person中的原型,那么_proto_中的构造函数也就是person构造函数(印证了我们上一条的观点)


1. 当new了构造函数之后,和原构造函数没有什么关系了,它只和原构造函数中的原型有关系;
2. 当我们new了多个构造函数之后,它们的对象的隐式原型都指向原构造函数的原型,因此它们共享原型中的属性和方法;
3. 当我们访问对象中的属性的时候,如果对象本身没有,回去对象中的原型中去查询,如果都没找到则返回未定义,如果对象本身和原型中有同样的属性和方法,会返回对象中的不会返回原型中的(查找的过程就叫原型链
4. 我们在对象中想这样修改原型中的值是不可能的:p1.name = "test"; 它只能读取原型中的值不能修改,这样操作是在本身的对象中添加一个name属性


证明它们

// 声明一个函数
function person(){};
person.prototype.name = "原型中的name";
person.prototype.age = "原型中的age";
// 1. new构造函数得出的新对象共享原型的属性和方法
var person1 = new person();
var person2 = new person();
console.log("new构造函数得出的新对象共享原型的属性和方法:" + person1.name); // 原型中的    name
console.log("new构造函数得出的新对象共享原型的属性和方法:"+ person1.name); // 原型中的name
// 2. 对象添加/修改属性不会操作原型中的
person1.others = "对象中的others";
console.log("对象添加/修改属性不会操作原型中的:" + person1.others); // 对象中的others
// 3. 对象和原型中存在同一个属性会返回对象中的
person1.name = "对象中的name";
console.log("对象和原型中存在同一个属性会返回对象中的:" + person1.name); // 对象中的name


关于原型的一些属性/方法

prototype属性: 存在于任何函数(不仅仅是构造函数,其他函数我们不关注而已),指向的是函数中的原型;
construtor属性:指向的是原构造函数;
  function person(){};  
    console.log(person.prototype.construtor === person); // true
    var person1 = new person();
    console.log(person1 instanceof person); // true

如果要重新赋值新的prototype,比如这样:

person.prototype = {namespace: "1"};

那么会造成这样的情况:

var person1 = new person();
console.log(person.prototype.construtor === person); // false

为了避免这样的情况(构造函数指向不正确):

person.prototype = {construtor: person, namespace: "1"};


_proto_:隐式原型

所有对象都有一个隐式原型,指向了构造函数的原型,这个原型不可访问,只有在谷歌浏览器,firefox浏览器支持直接._proto_这样的方式去访问,尽量开发者不要操作_proto_,因为不慎会影响继承原型链;

var person1 = new person();
console.log(person1._proto_ === person.prototype); // true

hasOwnProperty:判断属性是否来自对象本身/还是继承了原型中的属性
var person1 = new person();
person1.name = "shenhao";
console.log("name属性是不是来自对象本身的呢?" + person1.hasOwnProperty("name")); // true
console.log("age属性是不是来自对象本身的呢?" + person1.hasOwnProperty("age")); // false
console.log("sex属性是不是来自对象本身的呢?" + person1.hasOwnProperty("sex")); // false

我们可以看到,hasOwnProperty只能判断对象中的属性,不能判断原型中的属性,而且如果对象中的属性如果为空那么也会返回false;

那么我们如何判断这个属性是原型上的属性呢?

in操作符:查找对象中的属性,如果查找不到会在原型中去查询
var person1 = new person();
person1.name = "shenhao";
person.prototype.sex = "男"
console.log("sex" in person1); // true

但是有一个问题,in操作符查询对象中的属性,如果找到了就会返回对象中的,就不会去查询原型中的了,所以我们这个方案也失败;

所以我们可以用2者结合的方式去写一个函数:

function hasProto(obj, context){
    if(!(context in obj)){
        // 如果原型和对象中都没有属性
        console.log(`${context}不存在${obj}的原型和对象上`)
    }else if(obj.hasOwnProperty(context)){
        console.log(`${context}存在${obj}的对象上`)
    }else {
        // in操作符查询了(成功),但是hasOwnProperty查找却是失败,那么此属性在原型中存在
        console.log(`${context}存在${obj}的原型上`)
    }
}

创建对象的几种“模式”

原型的模式,如果在原型中创建了几个属性和方法,所有的构造函数出来的对象都会共享它们,方法共享比较nice,但是属性共享往往是不符合业务的,所以这是它最大的一个缺陷

构造函数的模式:每一个构造函数都会创建自己独立的属性和函数,属性自己独一份这没有错误,但是可能同样的函数要写很多份,势必会造成浪费;

但是在构造函数的模式,我们可以对function进行一个封装

function person(){
    this.name = "shenhao";
    this.age = "12";
    this.story = story;
};

function story(){
    return "一个开心的故事"
}

var person1 = new person();
console.log(person1.story()); //一个开心的故事

作为面向对象的语言,为了性能把方法从对象抽离出来,是非常emmmmm的,你们懂的

组合模式: 封装构造函数(传参)+ 原型模式
function person(name, age){
    this.name = name;
    this.age = age;
};

person.prototype.story = function(context){
    return context;
}

var person1 = new person("shenhao", "18");
console.log(person1.story("123"));
动态原型模式: 更优的解决方案,把构造函数和原型都写在了构造函数中
// 声明一个函数
function person(name, age){
    this.name = name;
    this.age = age;
    if(typeof this.story !== "function"){
        // 说明是第一次创建这个对象, 在原型上绑定
        person.prototype.story = (context) => {
            return this.name + context
        }
    }
};

var person1 = new person("shenhao", "18");
console.log(person1.story("你大爷"));


ok, 全部结束,希望你能有所收获

大家都知道原型和原型链是 JavaScript 中最经典的问题之一,而构造函数又是原型和原型链的基础,所以先了解清楚构造函数以及它的执行过程可以更好地帮助我们学习原型和原型链的知识。
本文将从以下几个方面来探讨构造函数:
1.什么是构造函数
2.为什么要使用构造函数
3.构造函数的执行过程
4.构造函数的返回值
1.什么是构造函数
在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数。
2.为什么要使用构造函数
学习每一个概念,不仅要知道它是什么,还要知道为什么,以及解决什么样的问题。
举个例子,我们要录入一年级一班中每一位同学的个人信息,那么我们可以创建一些对象,比如:
var p1 = { name: 'zs', age: 6, gender: '男', hobby: 'basketball' };
var p2 = { name: 'ls', age: 6, gender: '女', hobby: 'dancing' };
var p3 = { name: 'ww', age: 6, gender: '女', hobby: 'singing' };
var p4 = { name: 'zl', age: 6, gender: '男', hobby: 'football' };
// ...

像上面这样,我们可以把每一位同学的信息当做一个对象来处理。但是,我们会发现,我们重复地写了很多无意义的代码。比如 name、age、gender、hobby 。如果这个班上有60个学生,我们得重复写60遍。
这个时候,构造函数的优势就体现出来了。我们发现,虽然每位同学都有 name、gender、hobby 这些属性, 但它们都是不同的,那我们就把这些属性当做构造函数的参数传递进去。而由于都是一年级的学生,age 基本都是6岁,所以我们就可以写死,遇到特殊情况再单独做处理即可。此时,我们就可以创建以下的函数:
function Person(name, gender, hobby) {

this.name = name;
this.gender = gender;
this.hobby = hobby;
this.age = 6;

}

当创建上面的函数以后, 我们就可以通过 new 关键字调用,也就是通过构造函数来创建对象了。
var p1 = new Person('zs', '男', 'basketball');
var p2 = new Person('ls', '女', 'dancing');
var p3 = new Person('ww', '女', 'singing');
var p4 = new Person('zl', '男', 'football');
// ...

此时你会发现,创建对象会变得非常方便。所以,虽然封装构造函数的过程会比较麻烦,但一旦封装成功,我们再创建对象就会变得非常轻松,这也是我们为什么要使用构造函数的原因。
在使用对象字面量创建一系列同一类型的对象时,这些对象可能具有一些相似的特征(属性)和行为(方法),此时会产生很多重复的代码,而使用构造函数就可以实现代码的复用。
3.构造函数的执行过程
先说一点基本概念。
function Animal(color) {
this.color = color;
}

当一个函数创建好以后,我们并不知道它是不是构造函数,即使像上面的例子一样,函数名为大写,我们也不能确定。只有当一个函数以 new 关键字来调用的时候,我们才能说它是一个构造函数。就像下面这样:
var dog = new Animal("black");

以下我们只讨论构造函数的执行过程,也就是以 new 关键字来调用的情况。
我们还是以上面的 Person 为例。
function Person(name, gender, hobby) {
this.name = name;
this.gender = gender;
this.hobby = hobby;
this.age = 6;
}

var p1 = new Person('zs', '男', 'basketball');

此时,构造函数会有以下几个执行过程:
(1) 当以 new 关键字调用时,会创建一个新的内存空间,标记为 Animal 的实例。

1 创建内存空间.png

(2) 函数体内部的 this 指向该内存

2 函数体内部的 this 指向该内存.png

通过以上两步,我们就可以得出这样的结论。
var p2 = new Person('ls', '女', 'dancing'); // 创建一个新的内存 #f2
var p3 = new Person('ww', '女', 'singing'); // 创建一个新的内存 #f3

每当创建一个实例的时候,就会创建一个新的内存空间(#f2, #f3),创建 #f2 的时候,函数体内部的 this 指向 #f2, 创建 #f3 的时候,函数体内部的 this 指向 #f3。
(3) 执行函数体内的代码
通过上面的讲解,你就可以知道,给 this 添加属性,就相当于给实例添加属性。
(4) 默认返回 this 。
由于函数体内部的 this 指向新创建的内存空间,默认返回 this ,就相当于默认返回了该内存空间,也就是上图中的 #f1。此时,#f1的内存空间被变量 p1 所接受。也就是说 p1 这个变量,保存的内存地址就是 #f1,同时被标记为 Person 的实例。
以上就是构造函数的整个执行过程。
4.构造函数的返回值
构造函数执行过程的最后一步是默认返回 this 。言外之意,构造函数的返回值还有其它情况。下面我们就来聊聊关于构造函数返回值的问题。
(1)没有手动添加返回值,默认返回 this 。
function Person1() {
this.name = 'zhangsan';
}

var p1 = new Person1();

按照上面讲的,我们复习一遍。首先,当用 new 关键字调用时,产生一个新的内存空间 #f11,并标记为 Person1 的实例;接着,函数体内部的 this 指向该内存空间 #f11;执行函数体内部的代码;由于函数体内部的 this 指向该内存空间,而该内存空间又被变量 p1 所接收,所以 p1 中就会有一个 name 属性,属性值为 'zhangsan'。
p1: {
name: 'zhangsan'
}

(2) 手动添加一个基本数据类型的返回值,最终还是返回 this。
function Person2() {
this.age = 28;
return 50;
}

var p2 = new Person2();
console.log(p2.age); // 28

p2: {
age: 28
}

如果上面是一个普通函数的调用,那么返回值就是 50。
(3) 手动添加一个复杂数据类型(对象)的返回值,最终返回该对象
直接上例子
function Person3() {
this.height = '180';
return ['a', 'b', 'c'];
}

var p3 = new Person3();
console.log(p3.height); // undefined
console.log(p3.length); // 3
console.log(p3[0]); // 'a'

再来一个例子
function Person4() {
this.gender = '男';
return { gender: '中性' };
}

var p4 = new Person4();
console.log(p4.gender); // '中性'

关于构造函数的返回值,无非就是以上几种情况,大家可以动手试一试,也就记住了。
最后总结一下,本文从四个方面介绍了构造函数,而构造函数是原型和原型链学习的基础,所以大家有必要花点时间好好学习一下关于构造函数的知识,下篇文章我会来讲讲人人都能看懂的原型链,敬请期待。
最后的最后,我所说的不一定都对,你一定要自己试试!

作者:李等等扣丁
链接:https://www.jianshu.com/p/95a5faee17f1
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

原文链接: https://www.jianshu.com/p/95a5faee17f1

head标签本身并不携带任何东西,他主要是作为盛放其他语义化的容器使用;
HTML规定了在html标签中head标签必须是第一个;且head标签中必须存在title标签作为网站的标题,最多只能包含一个base,如果文档有其他方式指定了title,比如ifrme框架,可以不指定title;

我们来讨论一下title,title是标题的意思,但是html中也有一个h1-h6 heading也是标题头部的意思,在中文来讲,它们非常的难以分辨,更别说老外了;
其实HTML研发的时候也讨论了这个问题,解决方案呢就是:title作为元信息标签可能会被用来分享卡片,收藏书签的作用,所以title起一个概括全文的作用,但是h1有上下文链接的作用,只是展示页面即使无法全写,也不会有太大影响

我们再来谈谈一个冷门标签base是HTML历史遗留的标签,base是提供了文档url的一个基准,在base中指定url,我们在全文档体重就可以不用写前缀url,但是这个标签非常的危险,容易和js冲突,所以不建议使用;

下面我们聊聊meta标签,最重中之重的标签,他是一组键值对,它是通用的元信息表示标签;
一般的meta是由name和content两个属性来定义,name表示元信息的名字,content表示元信息的值


表示页面所在的web-application的名字是IsForums;

html5引入了charset来设置编码;没有content和name

这个非常重要,所以我建议放在文档的第一个;

浏览器读HTML是ASCLL字符,众所周知ASCLL是UTF和大多数编码格式的子集;一般清空下,服务端会通过http头来指定正确的编码方式,但是有些特殊的情况事用file协议打开html,如果没http头,就得设置编码啦;

具有http-equiv属性的meta;

具有equiv的meta,表示执行一个命令,这样的meta不需要name属性;

name 为 viewport 的 meta


我们的content中的内容是键值对的方式,没有在html标准定义viewport,双方约定好就可以自由使用;

QQ截图20190330104716.png

其他预定义的meta的name值:

QQ截图20190330104844.png

文章梗概:head元信息容器,title文档标题,base页面基准url,meta元信息通用标签

在评论区找到了一批干货:推荐大家看一下



常见的还有

format-detection 禁止 iPhone 的自动识别-》



ps:关于name为viewport不是HTML规定,你找不到的,是行业规定,但是非常之重要;